From 2d99d1d4783171c9751850a00de0cb355697e35b Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Mon, 10 May 2021 02:04:59 +0200 Subject: [PATCH] Generalize optional references and init data Added extension methods to query data from ECS objects Added base class for ECS objects Added support for representing in-construction ECS objects with an OptionalRef --- TechbloxModdingAPI/Block.cs | 10 ++-- TechbloxModdingAPI/Blocks/BlockEngine.cs | 19 ++++--- .../BlockEngineInit.cs => EcsObjectBase.cs} | 30 +++++++---- TechbloxModdingAPI/Utility/ApiExtensions.cs | 53 +++++++++++++++++++ TechbloxModdingAPI/Utility/OptionalRef.cs | 40 ++++++++++---- 5 files changed, 120 insertions(+), 32 deletions(-) rename TechbloxModdingAPI/{Blocks/BlockEngineInit.cs => EcsObjectBase.cs} (58%) create mode 100644 TechbloxModdingAPI/Utility/ApiExtensions.cs diff --git a/TechbloxModdingAPI/Block.cs b/TechbloxModdingAPI/Block.cs index 4729a91..f99ca28 100644 --- a/TechbloxModdingAPI/Block.cs +++ b/TechbloxModdingAPI/Block.cs @@ -20,7 +20,7 @@ namespace TechbloxModdingAPI /// A single (perhaps scaled) block. Properties may return default values if the block is removed and then setting them is ignored. /// For specific block type operations, use the specialised block classes in the TechbloxModdingAPI.Blocks namespace. /// - public class Block : IEquatable, IEquatable + public class Block : EcsObjectBase, IEquatable, IEquatable { protected static readonly PlacementEngine PlacementEngine = new PlacementEngine(); protected static readonly MovementEngine MovementEngine = new MovementEngine(); @@ -78,8 +78,7 @@ namespace TechbloxModdingAPI var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire); var egid = initializer.EGID; var bl = New(egid.entityID, egid.groupID); - bl.InitData.Group = BlockEngine.InitGroup(initializer); - bl.InitData.Reference = initializer.reference; + bl.InitData = initializer; Placed += bl.OnPlacedInit; return bl; } @@ -241,13 +240,12 @@ namespace TechbloxModdingAPI throw new BlockException("Blocks can only be placed in build mode."); var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire); Id = initializer.EGID; - InitData.Group = BlockEngine.InitGroup(initializer); + InitData = initializer; Placed += OnPlacedInit; } - public EGID Id { get; } + public override EGID Id { get; } - internal BlockEngine.BlockInitData InitData; private EGID copiedFrom; /// diff --git a/TechbloxModdingAPI/Blocks/BlockEngine.cs b/TechbloxModdingAPI/Blocks/BlockEngine.cs index 404e72a..7bade49 100644 --- a/TechbloxModdingAPI/Blocks/BlockEngine.cs +++ b/TechbloxModdingAPI/Blocks/BlockEngine.cs @@ -9,6 +9,7 @@ using RobocraftX.Blocks; using RobocraftX.Common; using RobocraftX.Physics; using RobocraftX.Rendering; +using RobocraftX.Rendering.GPUI; using Svelto.ECS.EntityStructs; using Svelto.DataStructures; @@ -16,6 +17,7 @@ using Svelto.ECS; using Svelto.ECS.Hybrid; using Unity.Mathematics; using TechbloxModdingAPI.Engines; +using TechbloxModdingAPI.Utility; namespace TechbloxModdingAPI.Blocks { @@ -68,14 +70,13 @@ namespace TechbloxModdingAPI.Blocks : entitiesDB.QueryEntity(index, CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour; - public ref T GetBlockInfo(EGID blockID) where T : unmanaged, IEntityComponent + public OptionalRef GetBlockInfo(EGID blockID) where T : unmanaged, IEntityComponent { - if (entitiesDB.Exists(blockID)) - return ref entitiesDB.QueryEntity(blockID); - T[] structHolder = new T[1]; //Create something that can be referenced - return ref structHolder[0]; //Gets a default value automatically + return entitiesDB.TryQueryEntitiesAndIndex(blockID, out uint index, out var array) + ? new OptionalRef(array, index) + : new OptionalRef(); } - + public ref T GetBlockInfoViewStruct(EGID blockID) where T : struct, INeedEGID, IEntityViewComponent { if (entitiesDB.Exists(blockID)) @@ -149,6 +150,12 @@ namespace TechbloxModdingAPI.Blocks entitiesDB.QueryEntity(id).matrix = float4x4.TRS(pos.position, rot.rotation, scale.scale); } + internal void UpdatePrefab(Block block, ushort type, byte material, bool flipped) + { + uint pid = PrefabsID.GetOrCreatePrefabID(type, material, 0, flipped); + entitiesDB.QueryEntityOrDefault() + } + public bool BlockExists(EGID blockID) { return entitiesDB.Exists(blockID); diff --git a/TechbloxModdingAPI/Blocks/BlockEngineInit.cs b/TechbloxModdingAPI/EcsObjectBase.cs similarity index 58% rename from TechbloxModdingAPI/Blocks/BlockEngineInit.cs rename to TechbloxModdingAPI/EcsObjectBase.cs index b9b9eae..c96cf25 100644 --- a/TechbloxModdingAPI/Blocks/BlockEngineInit.cs +++ b/TechbloxModdingAPI/EcsObjectBase.cs @@ -1,33 +1,43 @@ -using System; +using System; using System.Linq.Expressions; - using Svelto.DataStructures; using Svelto.ECS; using Svelto.ECS.Internal; +using TechbloxModdingAPI.Blocks; -namespace TechbloxModdingAPI.Blocks +namespace TechbloxModdingAPI { - public partial class BlockEngine + public abstract class EcsObjectBase { + public abstract EGID Id { get; } //Abstract to support the 'place' Block constructor + + protected internal EcsInitData InitData; + /// /// Holds information needed to construct a component initializer /// - internal struct BlockInitData + protected internal struct EcsInitData { - public FasterDictionary Group; - public EntityReference Reference; + private FasterDictionary group; + private EntityReference reference; + + public static implicit operator EcsInitData(EntityInitializer initializer) => new EcsInitData + {group = GetInitGroup(initializer), reference = initializer.reference}; + + public EntityInitializer Initializer(EGID id) => new EntityInitializer(id, group, reference); + public bool Valid => group != null; } - internal delegate FasterDictionary GetInitGroup( + private delegate FasterDictionary GetInitGroupFunc( EntityInitializer initializer); /// /// Accesses the group field of the initializer /// - internal GetInitGroup InitGroup = CreateAccessor("_group"); + private static GetInitGroupFunc GetInitGroup = CreateAccessor("_group"); //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection - internal static TDelegate CreateAccessor(string memberName) where TDelegate : Delegate + private static TDelegate CreateAccessor(string memberName) where TDelegate : Delegate { var invokeMethod = typeof(TDelegate).GetMethod("Invoke"); if (invokeMethod == null) diff --git a/TechbloxModdingAPI/Utility/ApiExtensions.cs b/TechbloxModdingAPI/Utility/ApiExtensions.cs new file mode 100644 index 0000000..3da1a67 --- /dev/null +++ b/TechbloxModdingAPI/Utility/ApiExtensions.cs @@ -0,0 +1,53 @@ +using Svelto.ECS; +using TechbloxModdingAPI.Blocks; + +namespace TechbloxModdingAPI.Utility +{ + public static class ApiExtensions + { + /// + /// Attempts to query an entity and returns an optional that contains the result if succeeded. + /// + /// The entities DB + /// The EGID to query + /// The component type to query + /// An optional that contains the result on success or is empty if not found + public static OptionalRef QueryEntityOptional(this EntitiesDB entitiesDB, EGID egid) + where T : unmanaged, IEntityComponent + { + return entitiesDB.TryQueryEntitiesAndIndex(egid, out uint index, out var array) + ? new OptionalRef(array, index) + : new OptionalRef(); + } + + /// + /// Attempts to query an entity and returns the result or a dummy value that can be modified. + /// + /// + /// + /// + /// + public static OptionalRef QueryEntityOptional(this EntitiesDB entitiesDB, EcsObjectBase obj) + where T : unmanaged, IEntityComponent + { + var opt = QueryEntityOptional(entitiesDB, obj.Id); + return opt ? opt : new OptionalRef(obj); + } + + /// + /// Attempts to query an entity and returns the result or a dummy value that can be modified. + /// + /// + /// + /// + /// + public static ref T QueryEntityOrDefault(this EntitiesDB entitiesDB, EcsObjectBase obj) + where T : unmanaged, IEntityComponent + { + var opt = QueryEntityOptional(entitiesDB, obj.Id); + if (opt) return ref opt.Get(); + if (obj.InitData.Valid) return ref obj.InitData.Initializer(obj.Id).GetOrCreate(); + return ref opt.Get(); //Default value + } + } +} \ No newline at end of file diff --git a/TechbloxModdingAPI/Utility/OptionalRef.cs b/TechbloxModdingAPI/Utility/OptionalRef.cs index 7210d22..2323431 100644 --- a/TechbloxModdingAPI/Utility/OptionalRef.cs +++ b/TechbloxModdingAPI/Utility/OptionalRef.cs @@ -9,31 +9,51 @@ using Svelto.ECS; namespace TechbloxModdingAPI { - public struct OptionalRef where T : unmanaged + public ref struct OptionalRef where T : unmanaged, IEntityComponent { private bool exists; private NB array; private uint index; + private EntityInitializer initializer; public OptionalRef(NB array, uint index) { exists = true; this.array = array; this.index = index; + initializer = default; } - - public OptionalRef(ref T value) + + /// + /// Wraps the initializer data, if present. + /// + /// The object with the initializer + public OptionalRef(EcsObjectBase obj) { - exists = true; + if (obj.InitData.Valid) + { + initializer = obj.InitData.Initializer(obj.Id); + exists = true; + } + else + { + initializer = default; + exists = false; + } array = default; index = default; } - public ref T Get(T def = default) + /// + /// Returns the value or a default value if empty. Supports objects that are being initialized. + /// + /// The value or the default value + public ref T Get() { - if (exists) + if (!exists) return ref CompRefCache.Default; + if (initializer.EGID == EGID.Empty) return ref array[index]; - return ref CompRefCache._default; + return ref initializer.GetOrCreate(); } public bool Exists => exists; @@ -51,10 +71,10 @@ namespace TechbloxModdingAPI /// /// Creates an instance of a struct T that can be referenced. /// - /// The struct type to cache - private struct CompRefCache where T : unmanaged + /// The struct type to cache + private struct CompRefCache where TR : unmanaged { - public static T _default; + public static TR Default; } } } \ No newline at end of file