From 4dd8af72b22fabab1ced1fdd50aa649ef11eecb5 Mon Sep 17 00:00:00 2001 From: wuke65536 <2446214230@qq.com> Date: Mon, 6 Apr 2026 22:40:26 +0800 Subject: [PATCH] implement a super fast data component. --- .../Mod/Registry/DataComponentRegistry.cs | 192 ++++++++++++++++++ Celeste.Mod.mm/MonoModRules.Game.cs | 2 + Celeste.Mod.mm/MonoModRules.Mod.cs | 12 ++ 3 files changed, 206 insertions(+) create mode 100644 Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs diff --git a/Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs b/Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs new file mode 100644 index 000000000..c0ddb0430 --- /dev/null +++ b/Celeste.Mod.mm/Mod/Registry/DataComponentRegistry.cs @@ -0,0 +1,192 @@ +using Celeste.Mod.Registry; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Cecil.Rocks; +using Monocle; +using MonoMod; +using MonoMod.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using static MonoMod.MonoModRules; +using FieldAttributes = Mono.Cecil.FieldAttributes; +using MethodAttributes = Mono.Cecil.MethodAttributes; +#pragma warning disable CS0618 + +namespace MonoMod { + static partial class MonoModRules { + public static void PatchAllEntity(TypeDefinition t) { + static bool IsEntity(TypeDefinition t) { + while (true) { + if (t is null) { + return false; + } + if (t.Module.Assembly.Name.Name == "Celeste" && t.Name == "Entity" && t.Namespace == "Monocle" && !t.IsNested) { + return true; + } + t = t.BaseType?.SafeResolve(); + } + } + if (IsEntity(t)) { + template ??= RulesModule.Types.First(x => x.Name == nameof(TemplateEntity) && x.Namespace == nameof(MonoMod)); + ModuleDefinition module = t.Module; + FieldDefinition origslots = template.FindField(nameof(TemplateEntity.slots)); + var objarr = new ArrayType(module.TypeSystem.Object); + var slots = new FieldDefinition(TemplateEntity.slotsName, FieldAttributes.Private, objarr); + t.Fields.Add(slots); + + FieldDefinition origreg = template.FindField(nameof(TemplateEntity._registered)); + var registered = new FieldDefinition(TemplateEntity.registeredName, FieldAttributes.Private | FieldAttributes.Static, module.ImportReference(origreg.FieldType)); + t.Fields.Add(registered); + + MethodDefinition methodTemplate = template.FindMethod(nameof(TemplateEntity.ReadSlot)); + MethodDefinition method = methodTemplate.Clone(); + method.DeclaringType = null; + t.Methods.Add(method); + method.Name = TemplateEntity.readName; + method.Attributes &= ~MethodAttributes.MemberAccessMask; + method.Attributes |= MethodAttributes.Private; + method.Parameters[1].ParameterType = t; + foreach (Instruction item in method.Body.Instructions) { + if (item.Operand is FieldReference fr) { + if (fr.Name == origreg.Name) { + item.Operand = registered; + } else if (fr.Name == origslots.Name) { + item.Operand = slots; + } + } + // not complete but enough + if (item.Operand is GenericInstanceMethod mr) { + var nmr = new GenericInstanceMethod(mr.ElementMethod); + for (int i = 0; i < mr.GenericArguments.Count; i++) { + TypeReference p = mr.GenericArguments[i]; + if (p is GenericParameter gp) { + nmr.GenericArguments.Add(method.GenericParameters[gp.Position]); + } else { + nmr.GenericArguments.Add(p); + } + } + item.Operand = module.ImportReference(nmr, method); + } + if (item.Operand is IMetadataTokenProvider imtp) { + item.Operand = module.ImportReference(imtp); + } + } + } + } + static TypeDefinition template; + } +#nullable enable + [MonoModRemove] + internal class TemplateEntity { + internal const string slotsName = "```" + nameof(slots); + internal const string registeredName = "```" + nameof(_registered); + internal const string readName = "```" + nameof(ReadSlot); + internal object[]? slots = null!; + internal static List _registered = null!; + // note: keep it simple, especially be careful about generic method + internal static ref TRet ReadSlot(DataComponentRegistrySlotHolder slotHolder, TemplateEntity self) where TRet : class { + return ref slotHolder.ReadSlot(ref self.slots, _registered); + } + } +} + +namespace Celeste.Mod.Registry { + [Obsolete("don't reference it from user code.")] + public class DataComponentRegistrySlotHolder { + internal int slot; + public ref TRet ReadSlot(ref object[]? self, List reg) { + if (self is null) { + self = new object[reg.Count]; + } else if (self.Length <= slot) { + Array.Resize(ref self, reg.Count); + } + return ref Unsafe.As(ref self[slot]); + } + } + + public class RegistryInfo { + [AllowNull] public string ModName; + [AllowNull] public string Description; + } + public static class DataComponentRegistry { + public delegate ref TRet Accessor(T self) where T : Entity where TRet : class?; + + /// + /// A performant data holder implementation. + /// Allows you to attach any data to a type of entity. + /// + /// + /// note: register new field and then access it *may* invalidate all existing references. + /// this can secretly happens in hook chain. + /// be careful with this one. + /// + /// Target entity type. + /// Attached data type. + /// + /// it's mainly for external tools, and not actually used anywhere. + /// type your modname and comment here. + /// + /// + /// if in debug mode, enable type check. + /// they should be not necessary, so you are allowed to disable them when publishing. + /// + /// The field accessor. note that your field can be null if it's not initialized. + public static Accessor RegisterFor([AllowNull] RegistryInfo info, bool debug) where T : Entity where TRet : class? { + BindingFlags bf = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly; + FieldInfo fieldInfo = typeof(T).GetField(TemplateEntity.registeredName, bf)!; + var arr = (List?) fieldInfo.GetValue(null); + if (arr is null) { + arr = new(); + fieldInfo.SetValue(null, arr); + } + var slot = new DataComponentRegistrySlotHolder() { slot = arr.Count }; + arr.Add(info); + + Accessor reader = + typeof(T) + .GetMethod(TemplateEntity.readName, bf)! + .MakeGenericMethod(typeof(TRet)) + .CreateDelegate>(slot); + + if (debug) { + Accessor _reader = reader; + reader = a => { + ref TRet? got = ref _reader(a); + if (got is { } && got.GetType() != typeof(TRet)) { + throw new InvalidCastException(); + } + return ref got; + }; + } + return reader; + } + /// + /// A performant data holder implementation. + /// Allows you to attach any data to a type of entity. + /// + /// + /// returns simple getter and setter. + /// + /// Target entity type. + /// Attached data type. + /// + /// it's mainly for external tools, and not actually used anywhere. + /// type your modname and comment here. + /// + /// + /// if in debug mode, enable type check. + /// they should be not necessary, so you are allowed to disable them when publishing. + /// + /// The field accessor. note that your field can be null if it's not initialized. + public static (Action setter, Func getter) RegisterForSimple([AllowNull] RegistryInfo info, bool debug) where T : Entity where TRet : class? { + Accessor reader = RegisterFor(info, debug); + return (((e, v) => reader(e) = v), e => reader(e)); + } + } +} diff --git a/Celeste.Mod.mm/MonoModRules.Game.cs b/Celeste.Mod.mm/MonoModRules.Game.cs index 9839b5ea7..b8f9e6223 100644 --- a/Celeste.Mod.mm/MonoModRules.Game.cs +++ b/Celeste.Mod.mm/MonoModRules.Game.cs @@ -170,6 +170,8 @@ static void VisitType(TypeDefinition type, MethodReference stubAttrCtor) { if ((field.Attributes & FieldAttributes.Static) != 0) field.Attributes &= ~FieldAttributes.InitOnly; + PatchAllEntity(type); + // Stub out extern FMOD methods in headless mode if (MonoModRule.Flag.Get("Headless") && type.Namespace.StartsWith("FMOD")) { foreach (MethodDefinition method in type.Methods) { diff --git a/Celeste.Mod.mm/MonoModRules.Mod.cs b/Celeste.Mod.mm/MonoModRules.Mod.cs index 2b4dc0e7e..6999e0b1b 100644 --- a/Celeste.Mod.mm/MonoModRules.Mod.cs +++ b/Celeste.Mod.mm/MonoModRules.Mod.cs @@ -30,6 +30,18 @@ private static void InitModRules(MonoModder modder) { } } + static void VisitType(TypeDefinition type) { + PatchAllEntity(type); + + // Visit nested types + foreach (TypeDefinition nestedType in type.NestedTypes) + VisitType(nestedType); + } + + foreach (TypeDefinition type in modder.Module.Types) + VisitType(type); + + // If this is legacy MonoMod, relink against modern MonoMod if (isMonoMod && isLegacyMonoMod) { SetupLegacyMonoModRelinking(modder);