Skip to content

Commit 7648957

Browse files
committed
implement a fast enough data component.
1 parent 714732c commit 7648957

2 files changed

Lines changed: 115 additions & 1 deletion

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using Celeste.Mod.Registry;
2+
using Monocle;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
7+
#nullable enable
8+
9+
namespace Celeste.Mod.Registry {
10+
internal class DataComponentRegistrySlotHolder {
11+
internal required int depth;
12+
internal required int slot;
13+
internal required List<RegistryInfo?> registered;
14+
}
15+
16+
public class RegistryInfo {
17+
[AllowNull] public string ModName;
18+
[AllowNull] public string Description;
19+
}
20+
21+
public static class DataComponentRegistry {
22+
internal static int getHierarchyDepth(Type? self) {
23+
int depth = 0;
24+
while (self is not null) {
25+
self = self.BaseType;
26+
depth++;
27+
}
28+
return depth - 1;
29+
}
30+
31+
internal static ref TRet? ReadSlot<T, TRet>(this DataComponentRegistrySlotHolder slotHolder, T self) where T : patch_Entity where TRet : class? {
32+
self.slots ??= new object[getHierarchyDepth(self.GetType())][];
33+
ref object[]? curDepth = ref self.slots[slotHolder.depth];
34+
35+
if (curDepth is not { }) {
36+
curDepth = new object[slotHolder.registered.Count];
37+
} else if (curDepth.Length <= slotHolder.slot) {
38+
Array.Resize(ref curDepth, slotHolder.registered.Count);
39+
}
40+
return ref Unsafe.As<object, TRet?>(ref curDepth[slotHolder.slot]);
41+
}
42+
43+
public delegate ref TRet Accessor<T, TRet>(T self) where T : patch_Entity where TRet : class?;
44+
45+
static Dictionary<Type, List<RegistryInfo?>> infos = new();
46+
/// <summary>
47+
/// A performant data holder implementation.
48+
/// Allows you to attach any data to a type of entity.
49+
/// </summary>
50+
/// <remarks>
51+
/// note: register new field and then access it *may* invalidate all existing references.
52+
/// this can secretly happens in hook chain.
53+
/// be careful with this one.
54+
/// </remarks>
55+
/// <typeparam name="T">Target entity type.</typeparam>
56+
/// <typeparam name="TRet">Attached data type.</typeparam>
57+
/// <param name="info">
58+
/// it's mainly for external tools, and not actually used anywhere.
59+
/// type your modname and comment here.
60+
/// </param>
61+
/// <param name="debug">
62+
/// if in debug mode, enable type check.
63+
/// they should be not necessary, so you are allowed to disable them when publishing.
64+
/// </param>
65+
/// <returns>The field accessor. note that your field can be null if it's not initialized.</returns>
66+
public static Accessor<T, TRet?> RegisterFor<T, TRet>(RegistryInfo? info, bool debug) where T : patch_Entity where TRet : class? {
67+
if (!infos.TryGetValue(typeof(T), out List<RegistryInfo?>? list)) {
68+
list = new();
69+
infos.Add(typeof(T), list);
70+
}
71+
72+
var slot = new DataComponentRegistrySlotHolder() { slot = list.Count, registered = list, depth = getHierarchyDepth(typeof(T)) - 1, };
73+
list.Add(info);
74+
75+
Accessor<T, TRet?> reader = slot.ReadSlot<T, TRet>;
76+
77+
if (debug) {
78+
Accessor<T, TRet?> _reader = reader;
79+
reader = a => {
80+
ref TRet? got = ref _reader(a);
81+
if (got is { } && got.GetType() != typeof(TRet)) {
82+
throw new InvalidCastException();
83+
}
84+
return ref got;
85+
};
86+
}
87+
return reader;
88+
}
89+
/// <summary>
90+
/// A performant data holder implementation.
91+
/// Allows you to attach any data to a type of entity.
92+
/// </summary>
93+
/// <remarks>
94+
/// returns simple getter and setter.
95+
/// </remarks>
96+
/// <typeparam name="T">Target entity type.</typeparam>
97+
/// <typeparam name="TRet">Attached data type.</typeparam>
98+
/// <param name="info">
99+
/// it's mainly for external tools, and not actually used anywhere.
100+
/// type your modname and comment here.
101+
/// </param>
102+
/// <param name="debug">
103+
/// if in debug mode, enable type check.
104+
/// they should be not necessary, so you are allowed to disable them when publishing.
105+
/// </param>
106+
/// <returns>The field accessor. note that your field can be null if it's not initialized.</returns>
107+
public static (Action<T, TRet?> setter, Func<T, TRet?> getter) RegisterForSimple<T, TRet>(RegistryInfo? info, bool debug) where T : patch_Entity where TRet : class? {
108+
Accessor<T, TRet?> reader = RegisterFor<T, TRet>(info, debug);
109+
return (((e, v) => reader(e) = v), e => reader(e));
110+
}
111+
}
112+
}

Celeste.Mod.mm/Patches/Monocle/Entity.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
using System.ComponentModel;
66

77
namespace Monocle {
8-
class patch_Entity : Entity {
8+
public class patch_Entity : Entity {
9+
internal object[][] slots = null;
10+
911
public new Scene Scene {
1012
[MonoModIgnore]
1113
get;

0 commit comments

Comments
 (0)