From 67f32b8810dc814f116eb0ce63fa24570d006b4f Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Thu, 30 Mar 2023 01:17:31 +0200 Subject: [PATCH] Improved and fixed publish queue detection and block test - Made the PublishEntityChangesDelayed() method use the internals of Svelto.ECS to determine if it should wait -- I had this code for a while but it used too much reflection to my liking -- Now I made the reflection code nicer and a bit safer - Fixed float comparisons: last time I didn't actually used abs() for float3 but I found this even better method --- TechbloxModdingAPI/Blocks/BlockTests.cs | 39 +++++++++---- TechbloxModdingAPI/Blocks/Engine.cs | 4 +- .../Blocks/Engines/BlockEngine.cs | 9 +-- .../Blocks/Engines/BlueprintEngine.cs | 1 + .../Blocks/Engines/MovementEngine.cs | 1 + .../Blocks/Engines/PlacementEngine.cs | 1 + .../Blocks/Engines/RotationEngine.cs | 1 + .../Blocks/Engines/SignalEngine.cs | 1 + TechbloxModdingAPI/Players/PlayerEngine.cs | 1 + .../Utility/{ => ECS}/ManagedApiExtensions.cs | 4 +- .../NativeApiExtensions.EntitiesDBHelper.cs | 56 +++++++++++++++++++ .../Utility/{ => ECS}/NativeApiExtensions.cs | 35 +++--------- 12 files changed, 106 insertions(+), 47 deletions(-) rename TechbloxModdingAPI/Utility/{ => ECS}/ManagedApiExtensions.cs (97%) create mode 100644 TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.EntitiesDBHelper.cs rename TechbloxModdingAPI/Utility/{ => ECS}/NativeApiExtensions.cs (77%) diff --git a/TechbloxModdingAPI/Blocks/BlockTests.cs b/TechbloxModdingAPI/Blocks/BlockTests.cs index 1e39945..a0ee0e9 100644 --- a/TechbloxModdingAPI/Blocks/BlockTests.cs +++ b/TechbloxModdingAPI/Blocks/BlockTests.cs @@ -5,7 +5,6 @@ using System.Reflection; using DataLoader; using Svelto.Tasks; -using Svelto.Tasks.Enumerators; using Unity.Mathematics; using TechbloxModdingAPI.Tests; @@ -86,28 +85,44 @@ namespace TechbloxModdingAPI.Blocks { //Includes specialised block properties if (property.SetMethod == null) continue; - var testValues = new (Type, object, Predicate)[] + + bool3 Float3Compare(float3 a, float3 b) + { // From Unity reference code + return math.abs(b - a) < math.max( + 0.000001f * math.max(math.abs(a), math.abs(b)), + float.Epsilon * 8 + ); + } + bool4 Float4Compare(float4 a, float4 b) + { // From Unity reference code + return math.abs(b - a) < math.max( + 0.000001f * math.max(math.abs(a), math.abs(b)), + float.Epsilon * 8 + ); + } + + var testValues = new (Type, object, Predicate<(object Value, object Default)>)[] { //(type, default value, predicate or null for equality) (typeof(long), 3, null), (typeof(int), 4, null), - (typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon), - (typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon), - (typeof(bool), true, obj => (bool) obj), - (typeof(string), "Test", obj => (string) obj == "Test"), //String equality check - (typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)), + (typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon), + (typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon), + (typeof(bool), true, t => (bool) t.Value), + (typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check + (typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))), (typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null), - (typeof(float4), (float4) 5, obj => math.all((float4) obj - 5 < (float4) float.Epsilon)) + (typeof(float4), (float4) 5, t => math.all(Float4Compare((float4)t.Value, (float4)t.Default))) }; var propType = property.PropertyType; if (!propType.IsValueType) continue; - (object valueToUse, Predicate predicateToUse) = (null, null); + (object valueToUse, Predicate<(object Value, object Default)> predicateToUse) = (null, null); foreach (var (type, value, predicate) in testValues) { if (type.IsAssignableFrom(propType)) { valueToUse = value; - predicateToUse = predicate ?? (obj => Equals(obj, value)); + predicateToUse = predicate ?? (t => Equals(t.Value, t.Default)); break; } } @@ -116,7 +131,7 @@ namespace TechbloxModdingAPI.Blocks { var values = propType.GetEnumValues(); valueToUse = values.GetValue(values.Length / 2); - predicateToUse = val => Equals(val, valueToUse); + predicateToUse = t => Equals(t.Value, t.Default); } if (valueToUse == null) @@ -144,7 +159,7 @@ namespace TechbloxModdingAPI.Blocks continue; } var attr = property.GetCustomAttribute(); - if (!predicateToUse(got) && (attr == null || !Equals(attr.PossibleValue, got))) + if (!predicateToUse((got, valueToUse)) && (attr == null || !Equals(attr.PossibleValue, got))) { Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}."); yield break; diff --git a/TechbloxModdingAPI/Blocks/Engine.cs b/TechbloxModdingAPI/Blocks/Engine.cs index 6140b11..e3d7ade 100644 --- a/TechbloxModdingAPI/Blocks/Engine.cs +++ b/TechbloxModdingAPI/Blocks/Engine.cs @@ -23,7 +23,7 @@ namespace TechbloxModdingAPI.Blocks { } - /// + /*/// - TODO: Internal struct access /// Gets or sets the Engine's On property. May not be saved. /// public bool On @@ -377,6 +377,6 @@ namespace TechbloxModdingAPI.Blocks { BlockEngine.GetBlockInfo(this).manualToAutoGearCoolOffTime = value; } - } + }*/ } } diff --git a/TechbloxModdingAPI/Blocks/Engines/BlockEngine.cs b/TechbloxModdingAPI/Blocks/Engines/BlockEngine.cs index 824071f..fd16d2f 100644 --- a/TechbloxModdingAPI/Blocks/Engines/BlockEngine.cs +++ b/TechbloxModdingAPI/Blocks/Engines/BlockEngine.cs @@ -21,6 +21,7 @@ using Unity.Mathematics; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; using PrefabsID = RobocraftX.Common.PrefabsID; namespace TechbloxModdingAPI.Blocks.Engines @@ -125,7 +126,7 @@ namespace TechbloxModdingAPI.Blocks.Engines var skew = entitiesDB.QueryEntity(id); entitiesDB.QueryEntity(id).matrix = math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix); - entitiesDB.PublishEntityChangeDelayed(id, 30); // Signal a prefab change so it updates the render buffers + entitiesDB.PublishEntityChangeDelayed(id); // Signal a prefab change so it updates the render buffers } internal void UpdatePrefab(Block block, byte material, bool flipped) @@ -146,8 +147,8 @@ namespace TechbloxModdingAPI.Blocks.Engines entitiesDB.QueryEntityOrDefault(block).prefabID = prefabId; if (block.Exists) { - entitiesDB.PublishEntityChangeDelayed(block.Id, 30); - entitiesDB.PublishEntityChangeDelayed(block.Id, 30); + entitiesDB.PublishEntityChangeDelayed(block.Id); + entitiesDB.PublishEntityChangeDelayed(block.Id); ref BuildingActionComponent local = ref entitiesDB.QueryEntity(BuildingDroneUtility @@ -161,7 +162,7 @@ namespace TechbloxModdingAPI.Blocks.Engines public void UpdateBlockColor(EGID id) { - entitiesDB.PublishEntityChangeDelayed(id, 30); + entitiesDB.PublishEntityChangeDelayed(id); } public bool BlockExists(EGID blockID) diff --git a/TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs b/TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs index e40f998..f04b3a3 100644 --- a/TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs +++ b/TechbloxModdingAPI/Blocks/Engines/BlueprintEngine.cs @@ -21,6 +21,7 @@ using Svelto.ECS.Serialization; using Techblox.Blocks.Connections; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; using Unity.Collections; using Unity.Mathematics; using UnityEngine; diff --git a/TechbloxModdingAPI/Blocks/Engines/MovementEngine.cs b/TechbloxModdingAPI/Blocks/Engines/MovementEngine.cs index 34ab4d5..7f8acfd 100644 --- a/TechbloxModdingAPI/Blocks/Engines/MovementEngine.cs +++ b/TechbloxModdingAPI/Blocks/Engines/MovementEngine.cs @@ -7,6 +7,7 @@ using Unity.Transforms; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; namespace TechbloxModdingAPI.Blocks.Engines { diff --git a/TechbloxModdingAPI/Blocks/Engines/PlacementEngine.cs b/TechbloxModdingAPI/Blocks/Engines/PlacementEngine.cs index 4d363a4..d32f7f1 100644 --- a/TechbloxModdingAPI/Blocks/Engines/PlacementEngine.cs +++ b/TechbloxModdingAPI/Blocks/Engines/PlacementEngine.cs @@ -16,6 +16,7 @@ using Unity.Mathematics; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; namespace TechbloxModdingAPI.Blocks.Engines { diff --git a/TechbloxModdingAPI/Blocks/Engines/RotationEngine.cs b/TechbloxModdingAPI/Blocks/Engines/RotationEngine.cs index a047031..ccfc13e 100644 --- a/TechbloxModdingAPI/Blocks/Engines/RotationEngine.cs +++ b/TechbloxModdingAPI/Blocks/Engines/RotationEngine.cs @@ -7,6 +7,7 @@ using UnityEngine; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; namespace TechbloxModdingAPI.Blocks.Engines { diff --git a/TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs b/TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs index e2009b4..45d6e75 100644 --- a/TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs +++ b/TechbloxModdingAPI/Blocks/Engines/SignalEngine.cs @@ -6,6 +6,7 @@ using Svelto.ECS; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; namespace TechbloxModdingAPI.Blocks.Engines { diff --git a/TechbloxModdingAPI/Players/PlayerEngine.cs b/TechbloxModdingAPI/Players/PlayerEngine.cs index 534f75a..a1965fd 100644 --- a/TechbloxModdingAPI/Players/PlayerEngine.cs +++ b/TechbloxModdingAPI/Players/PlayerEngine.cs @@ -21,6 +21,7 @@ using Techblox.Character; using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Utility; +using TechbloxModdingAPI.Utility.ECS; namespace TechbloxModdingAPI.Players { diff --git a/TechbloxModdingAPI/Utility/ManagedApiExtensions.cs b/TechbloxModdingAPI/Utility/ECS/ManagedApiExtensions.cs similarity index 97% rename from TechbloxModdingAPI/Utility/ManagedApiExtensions.cs rename to TechbloxModdingAPI/Utility/ECS/ManagedApiExtensions.cs index cf61e14..e689666 100644 --- a/TechbloxModdingAPI/Utility/ManagedApiExtensions.cs +++ b/TechbloxModdingAPI/Utility/ECS/ManagedApiExtensions.cs @@ -1,9 +1,7 @@ -using System.Collections; -using System.Collections.Generic; using Svelto.ECS; using Svelto.ECS.Hybrid; -namespace TechbloxModdingAPI.Utility +namespace TechbloxModdingAPI.Utility.ECS { public static class ManagedApiExtensions { diff --git a/TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.EntitiesDBHelper.cs b/TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.EntitiesDBHelper.cs new file mode 100644 index 0000000..5922478 --- /dev/null +++ b/TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.EntitiesDBHelper.cs @@ -0,0 +1,56 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection; +using HarmonyLib; +using Svelto.DataStructures; +using Svelto.ECS; + +namespace TechbloxModdingAPI.Utility.ECS +{ + public static partial class NativeApiExtensions + { + [SuppressMessage("ReSharper", "StaticMemberInGenericType")] + private static class EntitiesDBHelper where T : unmanaged, IEntityComponent + { // Each type gets a new set of fields here (that's what the ReSharper warning is about too) + public static readonly Lazy EntityStream = + new(() => AccessTools.PropertyGetter(typeof(EntitiesDB), "_entityStream")); + + public static readonly Lazy Streams = new(() => + AccessTools.Field(EntityStream.Value.ReturnType, "_streams")); + + public static readonly Lazy Consumers = new(() => + AccessTools.Field(typeof(EntityStream), "_consumers")); + + public static readonly Lazy TryGetValue = + new(AccessTools.Method(Streams.Value.FieldType, "TryGetValue")); + + public static readonly Lazy RingBuffer = + new(() => AccessTools.Field(typeof(Consumer), "_ringBuffer")); + } + + private static EntityStream GetEntityStream(this EntitiesDB entitiesDB) where T : unmanaged, IEntityComponent + { + // EntitiesStreams (internal) + var entitiesStreams = EntitiesDBHelper.EntityStream.Value.Invoke(entitiesDB, Array.Empty()); + // FasterDictionary (interface is internal) + var streams = EntitiesDBHelper.Streams.Value.GetValue(entitiesStreams); + + var parameters = new object[] { TypeRefWrapper.wrapper, null }; + var success = EntitiesDBHelper.TryGetValue.Value.Invoke(streams, parameters); + if (!(bool)success) + return null; // There is no entity stream for this type + return (EntityStream)parameters[1]; + } + + private static ThreadSafeFasterList> GetConsumers(this EntityStream stream) where T : unmanaged, IEntityComponent + { + return (ThreadSafeFasterList>)EntitiesDBHelper.Consumers.Value.GetValue(stream); + } + + private static RingBuffer<(T, EGID)> GetRingBuffer(this Consumer consumer) where T : unmanaged, IEntityComponent + { + return (RingBuffer<(T, EGID)>)EntitiesDBHelper.RingBuffer.Value.GetValue(consumer); + } + } +} \ No newline at end of file diff --git a/TechbloxModdingAPI/Utility/NativeApiExtensions.cs b/TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.cs similarity index 77% rename from TechbloxModdingAPI/Utility/NativeApiExtensions.cs rename to TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.cs index 47629bb..9cd0b64 100644 --- a/TechbloxModdingAPI/Utility/NativeApiExtensions.cs +++ b/TechbloxModdingAPI/Utility/ECS/NativeApiExtensions.cs @@ -1,16 +1,14 @@ using System; using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; using Svelto.DataStructures; using Svelto.ECS; using Svelto.Tasks; using Svelto.Tasks.Lean; using TechbloxModdingAPI.Tasks; -namespace TechbloxModdingAPI.Utility +namespace TechbloxModdingAPI.Utility.ECS { - public static class NativeApiExtensions + public static partial class NativeApiExtensions { /// /// Attempts to query an entity and returns an optional that contains the result if succeeded. @@ -69,36 +67,27 @@ namespace TechbloxModdingAPI.Utility return ref opt.Get(); //Default value } - private static readonly Dictionary Changes)> ChangesToPublish = new(); - /// /// Publishes an entity change, ignoring duplicate publishes and delaying changes as necessary. /// It will only publish in the next frame. /// /// The entities DB to publish to /// The ECS object that got changed - /// Limits how many changes to publish - should be no more than the consumers' capacity that process this component /// The component that changed - public static void PublishEntityChangeDelayed(this EntitiesDB entitiesDB, EGID id, int limit = 80) + public static void PublishEntityChangeDelayed(this EntitiesDB entitiesDB, EGID id) where T : unmanaged, IEntityComponent { - if (!ChangesToPublish.ContainsKey(typeof(T))) - ChangesToPublish.Add(typeof(T), (0, new HashSet())); - var changes = ChangesToPublish[typeof(T)].Changes; - if (changes.Contains(id)) return; - changes.Add(id); - PublishChanges(entitiesDB, id, limit).RunOn(Scheduler.leanRunner); + PublishChanges(entitiesDB, id).RunOn(Scheduler.leanRunner); } - private static IEnumerator PublishChanges(EntitiesDB entitiesDB, EGID id, int limit) + private static IEnumerator PublishChanges(EntitiesDB entitiesDB, EGID id) where T : unmanaged, IEntityComponent { yield return Yield.It; - while (ChangesToPublish[typeof(T)].PublishedCount >= limit) - yield return Yield.It; - if (!entitiesDB._entityStream._streams.TryGetValue(TypeRefWrapper.wrapper, out var result)) + var entityStream = entitiesDB.GetEntityStream(); + if (entityStream is null) yield break; // There is no entity stream for this type - var consumers = (result as EntityStream)?._consumers; + var consumers = entityStream.GetConsumers(); if (consumers == null) { Console.WriteLine("Consumers is null"); @@ -111,21 +100,15 @@ namespace TechbloxModdingAPI.Utility waitForConsumers = false; for (int i = 0; i < consumers.count; i++) { - var buffer = consumers[i]._ringBuffer; + var buffer = consumers[i].GetRingBuffer(); if (buffer.Count + 1 <= buffer.Capacity) continue; waitForConsumers = true; - Console.WriteLine($"Gonna have to wait for a consumer (capacity: {buffer.Capacity} count: {buffer.Count}"); break; } if (waitForConsumers) yield return Yield.It; } while (waitForConsumers); entitiesDB.PublishEntityChange(id); - var (count, changes) = ChangesToPublish[typeof(T)]; - changes.Remove(id); - ChangesToPublish[typeof(T)] = (count + 1, changes); - yield return Yield.It; - ChangesToPublish[typeof(T)] = (Math.Max(ChangesToPublish[typeof(T)].PublishedCount - 1, 0), changes); } ///