Skip to content

Commit aaeecc2

Browse files
committed
kinda working MPB management?
1 parent f9c40a0 commit aaeecc2

File tree

6 files changed

+383
-0
lines changed

6 files changed

+383
-0
lines changed
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
4+
namespace Shabby;
5+
6+
#nullable enable
7+
8+
internal class MpbCacheEntry
9+
{
10+
internal readonly MaterialPropertyBlock Mpb = new();
11+
internal readonly Dictionary<Props, List<int>> ManagedIds = [];
12+
}
13+
14+
internal class CompiledProps
15+
{
16+
private static readonly IEqualityComparer<SortedSet<Props>> CascadeKeyComparer =
17+
SortedSet<Props>.CreateSetComparer();
18+
19+
// FIXME: clear old entries...
20+
private static readonly Dictionary<SortedSet<Props>, MpbCacheEntry> mpbCache =
21+
new(CascadeKeyComparer);
22+
23+
internal static void Clear() => mpbCache.Clear();
24+
25+
private readonly SortedSet<Props> cascade = new(Props.PriorityComparer);
26+
27+
internal bool Add(Props props)
28+
{
29+
cachedMpb = null;
30+
return cascade.Add(props);
31+
}
32+
33+
private MaterialPropertyBlock? cachedMpb = null;
34+
35+
// Should this be a hashset?
36+
private static readonly List<Props> _dirtyProps = [];
37+
38+
internal static void UpdateDirtyProps()
39+
{
40+
foreach (var (cascade, cache) in mpbCache) {
41+
foreach (var props in cascade) {
42+
if (!props.Dirty) continue;
43+
_dirtyProps.Add(props);
44+
foreach (var managedId in cache.ManagedIds[props]) {
45+
props.Write(managedId, cache.Mpb);
46+
}
47+
}
48+
}
49+
50+
foreach (var props in _dirtyProps) props.Dirty = false;
51+
_dirtyProps.Clear();
52+
}
53+
54+
internal MaterialPropertyBlock Get()
55+
{
56+
if (cachedMpb != null) return cachedMpb;
57+
58+
if (!mpbCache.TryGetValue(cascade, out var cacheEntry)) {
59+
mpbCache[cascade] = cacheEntry = new MpbCacheEntry();
60+
61+
Dictionary<int, Props> idManagers = [];
62+
foreach (var props in cascade) {
63+
foreach (var id in props.ManagedIds) {
64+
idManagers[id] = props;
65+
}
66+
}
67+
68+
foreach (var (id, props) in idManagers) {
69+
if (!cacheEntry.ManagedIds.TryGetValue(props, out var ids)) {
70+
cacheEntry.ManagedIds[props] = ids = [];
71+
}
72+
73+
ids.Add(id);
74+
props.Write(id, cacheEntry.Mpb);
75+
}
76+
}
77+
78+
cachedMpb = cacheEntry.Mpb;
79+
return cachedMpb;
80+
}
81+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Collections.Generic;
2+
using UnityEngine;
3+
4+
namespace Shabby;
5+
6+
[KSPScenario(
7+
createOptions: ScenarioCreationOptions.AddToAllGames,
8+
tgtScenes: [GameScenes.LOADING, GameScenes.EDITOR, GameScenes.FLIGHT])]
9+
public sealed class MaterialPropertyManager : ScenarioModule
10+
{
11+
#region Fields
12+
13+
public static MaterialPropertyManager Instance { get; private set; }
14+
15+
private readonly Dictionary<Renderer, CompiledProps> compiledProperties = [];
16+
17+
#endregion
18+
19+
#region Lifecycle
20+
21+
public override void OnAwake()
22+
{
23+
name = nameof(MaterialPropertyManager);
24+
Instance = this;
25+
}
26+
27+
private void LateUpdate() => Refresh();
28+
29+
public void OnDestroy()
30+
{
31+
Instance = null;
32+
CompiledProps.Clear();
33+
}
34+
35+
#endregion
36+
37+
public void Set(Renderer renderer, Props props)
38+
{
39+
if (!compiledProperties.TryGetValue(renderer, out var compiledProps)) {
40+
compiledProperties[renderer] = compiledProps = new CompiledProps();
41+
}
42+
43+
compiledProps.Add(props);
44+
}
45+
46+
private static readonly List<Renderer> _deadRenderers = [];
47+
48+
private void Refresh()
49+
{
50+
CompiledProps.UpdateDirtyProps();
51+
52+
foreach (var (renderer, compiledProps) in compiledProperties) {
53+
if (renderer == null) {
54+
_deadRenderers.Add(renderer);
55+
continue;
56+
}
57+
58+
renderer.SetPropertyBlock(compiledProps.Get());
59+
}
60+
61+
foreach (var dead in _deadRenderers) {
62+
compiledProperties.Remove(dead);
63+
}
64+
}
65+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System.Collections.Generic;
2+
using System.Diagnostics;
3+
using System.Reflection;
4+
using HarmonyLib;
5+
using Highlighting;
6+
using KSPBuildTools;
7+
using UnityEngine;
8+
9+
namespace Shabby;
10+
11+
// [HarmonyPatch(typeof(Renderer))]
12+
// internal static class MaterialAccessWatchdog
13+
// {
14+
// [HarmonyPatch(nameof(Renderer.materials), MethodType.Getter)]
15+
// [HarmonyPostfix]
16+
// internal static void Renderer_materials_get_Postfix()
17+
// {
18+
// var trace = new StackTrace();
19+
// foreach (var frame in trace.GetFrames()!) {
20+
// var type = frame.GetMethod()?.DeclaringType;
21+
// if (type == typeof(KSP.UI.Screens.EditorPartIcon) ||
22+
// type == typeof(PSystemManager) ||
23+
// type == typeof(PSystemSetup) ||
24+
// type == typeof(Upgradeables.UpgradeableObject) ||
25+
// type == typeof(KSP.UI.Screens.Flight.NavBall)) {
26+
// return;
27+
// }
28+
// }
29+
//
30+
// Log.Debug($"Called `Renderer.materials`\n{trace}");
31+
// }
32+
// }
33+
34+
[HarmonyPatch]
35+
internal static class NoDuplicateMaterials
36+
{
37+
private static readonly MethodInfo mInfo_Renderer_material_get =
38+
AccessTools.PropertyGetter(typeof(Renderer), nameof(Renderer.material));
39+
40+
private static readonly MethodInfo mInfo_Renderer_materials_get =
41+
AccessTools.PropertyGetter(typeof(Renderer), nameof(Renderer.materials));
42+
43+
private static readonly MethodInfo mInfo_Renderer_sharedMaterial_get =
44+
AccessTools.PropertyGetter(typeof(Renderer), nameof(Renderer.sharedMaterial));
45+
46+
private static readonly MethodInfo mInfo_Renderer_sharedMaterials_get =
47+
AccessTools.PropertyGetter(typeof(Renderer), nameof(Renderer.sharedMaterials));
48+
49+
private static IEnumerable<MethodBase> TargetMethods() => [
50+
AccessTools.Method(typeof(Highlighter), "GrabRenderers"),
51+
AccessTools.Method(typeof(MaterialColorUpdater), "CreateRendererList"),
52+
AccessTools.Method(typeof(ModuleColorChanger), "ProcessMaterialsList"),
53+
AccessTools.Method(
54+
typeof(GameObjectExtension), nameof(GameObjectExtension.SetLayerRecursive),
55+
[typeof(GameObject), typeof(int), typeof(bool), typeof(int)])
56+
];
57+
58+
[HarmonyTranspiler]
59+
internal static IEnumerable<CodeInstruction> MaterialToSharedMaterialTranspiler(
60+
MethodBase targetMethod, IEnumerable<CodeInstruction> instructions)
61+
{
62+
foreach (var insn in instructions) {
63+
if (insn.Calls(mInfo_Renderer_material_get)) {
64+
insn.operand = mInfo_Renderer_sharedMaterial_get;
65+
Log.Debug("patched `Renderer.material` getter");
66+
} else if (insn.Calls(mInfo_Renderer_materials_get)) {
67+
insn.operand = mInfo_Renderer_sharedMaterials_get;
68+
Log.Debug("patched `Renderer.materials` getter");
69+
}
70+
71+
yield return insn;
72+
}
73+
}
74+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using System.Collections.Generic;
2+
using HarmonyLib;
3+
4+
namespace Shabby;
5+
6+
[HarmonyPatch(typeof(Part))]
7+
internal static class PartPatch
8+
{
9+
private static readonly Dictionary<Part, Props> highlightProperties = [];
10+
11+
[HarmonyPostfix]
12+
[HarmonyPatch("Awake")]
13+
private static void Awake_Postfix(Part __instance)
14+
{
15+
highlightProperties[__instance] = new Props(int.MinValue + 1);
16+
}
17+
18+
[HarmonyPostfix]
19+
[HarmonyPatch("CreateRendererLists")]
20+
private static void CreateRendererLists_Postfix(Part __instance)
21+
{
22+
var props = highlightProperties[__instance];
23+
props.SetFloat(PropertyIDs._RimFalloff, 2f);
24+
props.SetColor(PropertyIDs._RimColor, Part.defaultHighlightNone);
25+
foreach (var renderer in __instance.HighlightRenderer) {
26+
MaterialPropertyManager.Instance.Set(renderer, props);
27+
}
28+
}
29+
30+
[HarmonyPrefix]
31+
[HarmonyPatch(nameof(Part.SetOpacity))]
32+
private static bool SetOpacity_Prefix(Part __instance, float opacity)
33+
{
34+
__instance.CreateRendererLists();
35+
__instance.mpb.SetFloat(PropertyIDs._Opacity, opacity);
36+
highlightProperties[__instance].SetFloat(PropertyIDs._Opacity, opacity);
37+
return false;
38+
}
39+
40+
[HarmonyPostfix]
41+
[HarmonyPatch("OnDestroy")]
42+
private static void OnDestroy_Postfix(Part __instance)
43+
{
44+
highlightProperties.Remove(__instance);
45+
}
46+
}

Source/DynamicProperties/Props.cs

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
using System.Collections.Generic;
2+
using System.Runtime.CompilerServices;
3+
using KSPBuildTools;
4+
using UnityEngine;
5+
6+
namespace Shabby;
7+
8+
internal abstract class Prop;
9+
10+
internal class Prop<T>(T value) : Prop
11+
{
12+
internal T Value = value;
13+
14+
public override string ToString() => Value.ToString();
15+
}
16+
17+
public sealed class Props(int priority)
18+
{
19+
public readonly int Priority = priority;
20+
21+
private readonly Dictionary<int, Prop> _props = [];
22+
23+
internal bool Dirty = false;
24+
25+
private static uint _idCounter = 0;
26+
private static uint _nextId() => _idCounter++;
27+
private readonly uint _uniqueId = _nextId();
28+
29+
// Note that this is compatible with default object reference equality.
30+
public static readonly Comparer<Props> PriorityComparer = Comparer<Props>.Create((a, b) =>
31+
{
32+
var priorityCmp = a.Priority.CompareTo(b.Priority);
33+
return priorityCmp != 0 ? priorityCmp : a._uniqueId.CompareTo(b._uniqueId);
34+
});
35+
36+
internal IEnumerable<int> ManagedIds => _props.Keys;
37+
38+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
39+
private void _internalSet<T>(int id, T value)
40+
{
41+
Dirty = true;
42+
43+
MaterialPropertyManager.Instance.LogDebug($"setting {id} to {value}");
44+
45+
if (!_props.TryGetValue(id, out var prop)) {
46+
_props[id] = new Prop<T>(value);
47+
return;
48+
}
49+
50+
if (prop is not Prop<T> propT) {
51+
MaterialPropertyManager.Instance.LogWarning(
52+
$"property {id} has mismatched type; overwriting with {typeof(T).Name}!");
53+
_props[id] = new Prop<T>(value);
54+
return;
55+
}
56+
57+
propT.Value = value;
58+
}
59+
60+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
61+
public void SetColor(int id, Color value) => _internalSet<Color>(id, value);
62+
63+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
64+
public void SetFloat(int id, float value) => _internalSet<float>(id, value);
65+
66+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
67+
public void SetInt(int id, int value) => _internalSet<int>(id, value);
68+
69+
public void SetTexture(int id, Texture value) => _internalSet<Texture>(id, value);
70+
public void SetVector(int id, Vector4 value) => _internalSet<Vector4>(id, value);
71+
72+
private bool _internalHas<T>(int id) => _props.TryGetValue(id, out var prop) && prop is Prop<T>;
73+
74+
public bool HasColor(int id) => _internalHas<Color>(id);
75+
public bool HasFloat(int id) => _internalHas<float>(id);
76+
public bool HasInt(int id) => _internalHas<int>(id);
77+
public bool HasTexture(int id) => _internalHas<Texture>(id);
78+
public bool HasVector(int id) => _internalHas<Vector4>(id);
79+
80+
internal void Write(int id, MaterialPropertyBlock mpb)
81+
{
82+
if (!_props.TryGetValue(id, out var prop)) {
83+
throw new KeyNotFoundException($"property {id} not found");
84+
}
85+
86+
switch (prop) {
87+
case Prop<Color> c: mpb.SetColor(id, c.Value); break;
88+
case Prop<float> f: mpb.SetFloat(id, f.Value); break;
89+
case Prop<int> i: mpb.SetInt(id, i.Value); break;
90+
case Prop<Texture> t: mpb.SetTexture(id, t.Value); break;
91+
case Prop<Vector4> v: mpb.SetVector(id, v.Value); break;
92+
}
93+
}
94+
95+
public override string ToString()
96+
{
97+
var sb = StringBuilderCache.Acquire();
98+
sb.AppendFormat("(Priority {0}) {{\n", Priority);
99+
foreach (var (id, prop) in _props) {
100+
sb.AppendFormat("{0} = {1}\n", id, prop);
101+
}
102+
103+
sb.AppendLine("}");
104+
return sb.ToStringAndRelease();
105+
}
106+
}

0 commit comments

Comments
 (0)