Skip to content

Commit 16bb3a2

Browse files
committed
rewrite it again
...to a "push" architecture
1 parent f1295be commit 16bb3a2

File tree

6 files changed

+295
-157
lines changed

6 files changed

+295
-157
lines changed

Source/DynamicProperties/CompiledProps.cs

Lines changed: 0 additions & 114 deletions
This file was deleted.

Source/DynamicProperties/MaterialPropertyManager.cs

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public sealed class MaterialPropertyManager : MonoBehaviour
1111

1212
public static MaterialPropertyManager Instance { get; private set; }
1313

14-
private readonly Dictionary<Renderer, CompiledProps> compiledProperties = [];
14+
private readonly Dictionary<Renderer, PropsCascade> rendererCascades = [];
1515

1616
#endregion
1717

@@ -32,56 +32,55 @@ private void Awake()
3232
Instance = this;
3333
}
3434

35-
private void LateUpdate() => Refresh();
36-
3735
private void OnDestroy()
3836
{
3937
if (Instance != this) return;
4038

4139
Instance = null;
42-
CompiledProps.ClearCache();
40+
PropsCascade.ClearCache();
41+
42+
// Poor man's GC :'(
43+
MaterialColorUpdaterPatch.temperatureColorProps.Clear();
44+
ModuleColorChangerPatch.mccProps.Clear();
45+
4346
this.LogDebug("destroyed");
4447
}
4548

4649
#endregion
4750

4851
public bool Set(Renderer renderer, Props props)
4952
{
50-
if (!compiledProperties.TryGetValue(renderer, out var compiledProps)) {
51-
compiledProperties[renderer] = compiledProps = new CompiledProps();
53+
if (renderer == null) {
54+
Log.LogError(this, $"cannot set property on null renderer {renderer.GetHashCode()}");
55+
return false;
56+
}
57+
58+
if (!rendererCascades.TryGetValue(renderer, out var cascade)) {
59+
rendererCascades[renderer] = cascade = new PropsCascade(renderer);
5260
}
5361

54-
return compiledProps.Add(props);
62+
return cascade.Add(props);
5563
}
5664

5765
public bool Remove(Renderer renderer, Props props)
5866
{
59-
if (!compiledProperties.TryGetValue(renderer, out var compiledProps)) return false;
60-
return compiledProps.Remove(props);
67+
if (!rendererCascades.TryGetValue(renderer, out var cascade)) return false;
68+
return cascade.Remove(props);
6169
}
6270

63-
private static readonly List<Renderer> _deadRenderers = [];
64-
65-
private void Refresh()
71+
public bool Remove(Renderer renderer)
6672
{
67-
CompiledProps.RefreshChangedProps();
73+
return rendererCascades.Remove(renderer);
74+
}
6875

69-
foreach (var (renderer, compiledProps) in compiledProperties) {
70-
if (renderer == null) {
71-
this.LogDebug($"dead renderer {renderer.GetHashCode()}");
72-
_deadRenderers.Add(renderer);
73-
continue;
74-
}
76+
public bool Remove(Props props)
77+
{
78+
var removed = false;
7579

76-
if (!renderer.gameObject.activeInHierarchy) continue;
80+
foreach (var cascade in rendererCascades.Values) removed |= cascade.Remove(props);
7781

78-
if (compiledProps.GetIfChanged(out var mpb)) {
79-
this.LogDebug($"set mpb on renderer {renderer.name} {renderer.GetHashCode()}\n");
80-
renderer.SetPropertyBlock(mpb);
81-
}
82-
}
82+
PropsCascade.RemoveCacheEntriesWith(props);
8383

84-
foreach (var dead in _deadRenderers) compiledProperties.Remove(dead);
85-
_deadRenderers.Clear();
84+
return removed;
8685
}
8786
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#nullable enable
2+
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Runtime.CompilerServices;
6+
using KSPBuildTools;
7+
using UnityEngine;
8+
9+
namespace Shabby.DynamicProperties;
10+
11+
internal class MpbCompiler : IDisposable
12+
{
13+
/// Immutable.
14+
internal readonly SortedSet<Props> Cascade;
15+
16+
private readonly HashSet<Renderer> linkedRenderers = [];
17+
private readonly MaterialPropertyBlock mpb = new();
18+
private readonly Dictionary<Props, List<int>> idManagerMap = [];
19+
20+
private static readonly MaterialPropertyBlock EmptyMpb = new();
21+
22+
internal MpbCompiler(SortedSet<Props> cascades)
23+
{
24+
Cascade = cascades;
25+
RebuildManagerMap();
26+
RewriteMpb();
27+
foreach (var props in Cascade) {
28+
props.OnValueChanged += OnPropsValueChanged;
29+
props.OnEntriesChanged += OnPropsEntriesChanged;
30+
}
31+
}
32+
33+
internal void Register(Renderer renderer)
34+
{
35+
linkedRenderers.Add(renderer);
36+
Apply(renderer);
37+
}
38+
39+
internal void Unregister(Renderer renderer)
40+
{
41+
linkedRenderers.Remove(renderer);
42+
renderer.SetPropertyBlock(EmptyMpb);
43+
}
44+
45+
private void RebuildManagerMap()
46+
{
47+
idManagerMap.Clear();
48+
49+
Dictionary<int, Props> idManagers = [];
50+
foreach (var props in Cascade) {
51+
foreach (var id in props.ManagedIds) {
52+
idManagers[id] = props;
53+
}
54+
}
55+
56+
foreach (var (id, props) in idManagers) {
57+
if (!idManagerMap.TryGetValue(props, out var ids)) {
58+
idManagerMap[props] = ids = [];
59+
}
60+
61+
ids.Add(id);
62+
}
63+
}
64+
65+
private void OnPropsValueChanged(Props props)
66+
{
67+
WriteMpb(props);
68+
Apply();
69+
}
70+
71+
private void OnPropsEntriesChanged(Props props)
72+
{
73+
RebuildManagerMap();
74+
RewriteMpb();
75+
Apply();
76+
}
77+
78+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
79+
private void WriteMpb(Props props)
80+
{
81+
foreach (var id in idManagerMap[props]) props.Write(id, mpb);
82+
}
83+
84+
private void RewriteMpb()
85+
{
86+
mpb.Clear();
87+
foreach (var props in idManagerMap.Keys) WriteMpb(props);
88+
}
89+
90+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
91+
private void Apply(Renderer renderer) => renderer.SetPropertyBlock(mpb);
92+
93+
private readonly List<Renderer> _deadRenderers = [];
94+
95+
internal void Apply()
96+
{
97+
foreach (var renderer in linkedRenderers) {
98+
if (renderer == null) {
99+
_deadRenderers.Add(renderer!);
100+
continue;
101+
}
102+
103+
Apply(renderer);
104+
}
105+
106+
foreach (var dead in _deadRenderers) {
107+
MaterialPropertyManager.Instance.LogDebug($"dead renderer {dead.GetHashCode()}");
108+
MaterialPropertyManager.Instance.Remove(dead);
109+
}
110+
111+
if (linkedRenderers.Count == 0) {
112+
MaterialPropertyManager.Instance.LogDebug("dead cache entry");
113+
PropsCascade.RemoveCacheEntry(this);
114+
}
115+
}
116+
117+
private bool _disposed = false;
118+
119+
private void UnlinkProps()
120+
{
121+
if (_disposed) return;
122+
123+
Debug.Log("disposing MPB cache entry");
124+
125+
foreach (var props in Cascade) {
126+
props.OnValueChanged -= OnPropsValueChanged;
127+
props.OnEntriesChanged -= OnPropsEntriesChanged;
128+
}
129+
130+
_disposed = true;
131+
}
132+
133+
public void Dispose()
134+
{
135+
UnlinkProps();
136+
GC.SuppressFinalize(this);
137+
}
138+
139+
~MpbCompiler()
140+
{
141+
UnlinkProps();
142+
}
143+
}

Source/DynamicProperties/Patches/PartPatch.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ private static IEnumerable<CodeInstruction> Highlight_Transpiler(
122122
[HarmonyPatch("OnDestroy")]
123123
private static void OnDestroy_Postfix(Part __instance)
124124
{
125-
rimHighlightProps.Remove(__instance);
125+
if (rimHighlightProps.Remove(__instance, out var props)) {
126+
props.Dispose();
127+
}
126128
}
127129
}

0 commit comments

Comments
 (0)