diff --git a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
index a420ae955..d8d9252de 100644
--- a/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
+++ b/Celeste.Mod.mm/Mod/Everest/Everest.Loader.cs
@@ -211,6 +211,8 @@ internal static void LoadAuto() {
Logger.Verbose("loader", $"ALL MODS LOADED IN {watch.ElapsedMilliseconds}ms");
Logger.Info("loader", $"Loaded {Everest._Modules.Count} modules");
+ DataComponentRegistry.Optimize();
+
try {
Watcher = new FileSystemWatcher {
Path = PathMods,
diff --git a/Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs b/Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs
new file mode 100644
index 000000000..3e7245a0f
--- /dev/null
+++ b/Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs
@@ -0,0 +1,287 @@
+using Monocle;
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+#nullable enable
+
+namespace Celeste.Mod.Registry {
+ public class DataComponentInfo {
+ public string? ModName;
+ public string? Description;
+ }
+ internal struct DebugModeDataComponentInfo {
+ internal DataComponentInfo? info;
+ internal nint registryId;
+ internal bool unloaded;
+ internal int knownUnloaded;
+ }
+ internal struct DebugModeDataComponent {
+ internal object? content;
+ internal nint registryId;
+ }
+
+ ///
+ /// getter and setter for your registration.
+ ///
+ /// Target entity type.
+ /// Attached data type.
+ public abstract class DataComponentAccessor where T : patch_Entity where TRet : class? {
+ public TRet? GetValue(T entity) {
+ return GetValueRefUnsafe(entity);
+ }
+
+ public void SetValue(T entity, TRet? value) {
+ GetValueRefUnsafe(entity) = value;
+ }
+ ///
+ /// faster getter and setter.
+ ///
+ ///
+ /// some operations *may* invalidate all existing references.
+ /// they are:
+ /// in debug mode, register new field and then access it;
+ /// in release mode, access child class registration.
+ ///
+ /// this can secretly happen in hook chain.
+ /// be careful with this one,
+ /// or switch to simple getter and setter.
+ ///
+ public abstract ref TRet? GetValueRefUnsafe(T entity);
+ };
+
+ public static class DataComponentRegistry {
+ internal interface SlotHolderBase {
+ internal int slot { set; }
+ internal int knownCount { set; }
+ internal abstract Type declaringType { get; }
+ internal abstract Type fieldType { get; }
+ }
+
+ internal static int getHierarchyDepth(Type? self) {
+ int depth = 0;
+ while (self is not null) {
+ self = self.BaseType;
+ depth++;
+ }
+ return depth - 1;
+ }
+
+ internal static nint registryId = 1;
+ internal static readonly Dictionary> debugInfos = new();
+ internal static readonly Dictionary knownUnloadedIndex = new();
+
+ internal static readonly Dictionary> infos = new();
+ internal static Dictionary>? toOptimize = new();
+
+ internal static void Optimize() {
+ if (toOptimize is not { } toop) {
+ throw new InvalidOperationException("what did it mean");
+ }
+ toOptimize = null;
+ Dictionary types = new();
+
+ foreach ((Type k, List o) in infos) {
+ static int GetOrSet(Dictionary types, Type t, int? hint) {
+ if (types.TryGetValue(t, out int cur)) {
+ return cur;
+ }
+ int baseCnt;
+ if (t == typeof(Entity)) {
+ baseCnt = 0;
+ } else {
+ baseCnt = GetOrSet(types, t.BaseType!, null);
+ }
+ return types[t] = baseCnt + (hint ?? infos.GetValueOrDefault(t)?.Count ?? 0);
+ }
+ GetOrSet(types, k, o.Count);
+ }
+ foreach ((Type? k, List? v) in toop) {
+ int all = types[k];
+ int bas = all - v.Count;
+ for (int i = 0; i < v.Count; i++) {
+ v[i].slot = bas + i;
+ v[i].knownCount = all;
+ }
+ }
+ }
+
+ internal static void ThrowNotPrepared() {
+ throw new InvalidOperationException("It's not prepared.");
+ }
+ internal sealed class SlotHolder : DataComponentAccessor, SlotHolderBase
+ where T : patch_Entity where TRet : class? {
+ public override ref TRet? GetValueRefUnsafe(T self) {
+ if (slot < 0) {
+ ThrowNotPrepared();
+ }
+ ref object[] slots = ref self.slots;
+ if (slots is not { }) {
+ slots = new object[knownCount];
+ } else if (slots.Length <= slot) {
+ Array.Resize(ref slots, knownCount);
+ }
+ return ref Unsafe.As