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<T>
This commit is contained in:
Norbi Peti 2021-05-10 02:04:59 +02:00
parent 78ee3b3bcd
commit 2d99d1d478
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
5 changed files with 120 additions and 32 deletions

View file

@ -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. /// 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. /// For specific block type operations, use the specialised block classes in the TechbloxModdingAPI.Blocks namespace.
/// </summary> /// </summary>
public class Block : IEquatable<Block>, IEquatable<EGID> public class Block : EcsObjectBase, IEquatable<Block>, IEquatable<EGID>
{ {
protected static readonly PlacementEngine PlacementEngine = new PlacementEngine(); protected static readonly PlacementEngine PlacementEngine = new PlacementEngine();
protected static readonly MovementEngine MovementEngine = new MovementEngine(); protected static readonly MovementEngine MovementEngine = new MovementEngine();
@ -78,8 +78,7 @@ namespace TechbloxModdingAPI
var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire); var initializer = PlacementEngine.PlaceBlock(block, position, player, autoWire);
var egid = initializer.EGID; var egid = initializer.EGID;
var bl = New<T>(egid.entityID, egid.groupID); var bl = New<T>(egid.entityID, egid.groupID);
bl.InitData.Group = BlockEngine.InitGroup(initializer); bl.InitData = initializer;
bl.InitData.Reference = initializer.reference;
Placed += bl.OnPlacedInit; Placed += bl.OnPlacedInit;
return bl; return bl;
} }
@ -241,13 +240,12 @@ namespace TechbloxModdingAPI
throw new BlockException("Blocks can only be placed in build mode."); throw new BlockException("Blocks can only be placed in build mode.");
var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire); var initializer = PlacementEngine.PlaceBlock(type, position, player, autoWire);
Id = initializer.EGID; Id = initializer.EGID;
InitData.Group = BlockEngine.InitGroup(initializer); InitData = initializer;
Placed += OnPlacedInit; Placed += OnPlacedInit;
} }
public EGID Id { get; } public override EGID Id { get; }
internal BlockEngine.BlockInitData InitData;
private EGID copiedFrom; private EGID copiedFrom;
/// <summary> /// <summary>

View file

@ -9,6 +9,7 @@ using RobocraftX.Blocks;
using RobocraftX.Common; using RobocraftX.Common;
using RobocraftX.Physics; using RobocraftX.Physics;
using RobocraftX.Rendering; using RobocraftX.Rendering;
using RobocraftX.Rendering.GPUI;
using Svelto.ECS.EntityStructs; using Svelto.ECS.EntityStructs;
using Svelto.DataStructures; using Svelto.DataStructures;
@ -16,6 +17,7 @@ using Svelto.ECS;
using Svelto.ECS.Hybrid; using Svelto.ECS.Hybrid;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI.Blocks namespace TechbloxModdingAPI.Blocks
{ {
@ -68,14 +70,13 @@ namespace TechbloxModdingAPI.Blocks
: entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index, : entitiesDB.QueryEntity<PaletteEntryEntityStruct>(index,
CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour; CommonExclusiveGroups.COLOUR_PALETTE_GROUP).Colour;
public ref T GetBlockInfo<T>(EGID blockID) where T : unmanaged, IEntityComponent public OptionalRef<T> GetBlockInfo<T>(EGID blockID) where T : unmanaged, IEntityComponent
{ {
if (entitiesDB.Exists<T>(blockID)) return entitiesDB.TryQueryEntitiesAndIndex<T>(blockID, out uint index, out var array)
return ref entitiesDB.QueryEntity<T>(blockID); ? new OptionalRef<T>(array, index)
T[] structHolder = new T[1]; //Create something that can be referenced : new OptionalRef<T>();
return ref structHolder[0]; //Gets a default value automatically
} }
public ref T GetBlockInfoViewStruct<T>(EGID blockID) where T : struct, INeedEGID, IEntityViewComponent public ref T GetBlockInfoViewStruct<T>(EGID blockID) where T : struct, INeedEGID, IEntityViewComponent
{ {
if (entitiesDB.Exists<T>(blockID)) if (entitiesDB.Exists<T>(blockID))
@ -149,6 +150,12 @@ namespace TechbloxModdingAPI.Blocks
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix = float4x4.TRS(pos.position, rot.rotation, scale.scale); entitiesDB.QueryEntity<RenderingDataStruct>(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<GFXPrefabEntityStructGPUI>()
}
public bool BlockExists(EGID blockID) public bool BlockExists(EGID blockID)
{ {
return entitiesDB.Exists<DBEntityStruct>(blockID); return entitiesDB.Exists<DBEntityStruct>(blockID);

View file

@ -1,33 +1,43 @@
using System; using System;
using System.Linq.Expressions; using System.Linq.Expressions;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Internal; 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;
/// <summary> /// <summary>
/// Holds information needed to construct a component initializer /// Holds information needed to construct a component initializer
/// </summary> /// </summary>
internal struct BlockInitData protected internal struct EcsInitData
{ {
public FasterDictionary<RefWrapperType, ITypeSafeDictionary> Group; private FasterDictionary<RefWrapperType, ITypeSafeDictionary> group;
public EntityReference Reference; 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<RefWrapperType, ITypeSafeDictionary> GetInitGroup( private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
EntityInitializer initializer); EntityInitializer initializer);
/// <summary> /// <summary>
/// Accesses the group field of the initializer /// Accesses the group field of the initializer
/// </summary> /// </summary>
internal GetInitGroup InitGroup = CreateAccessor<GetInitGroup>("_group"); private static GetInitGroupFunc GetInitGroup = CreateAccessor<GetInitGroupFunc>("_group");
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection //https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
internal static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate private static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
{ {
var invokeMethod = typeof(TDelegate).GetMethod("Invoke"); var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
if (invokeMethod == null) if (invokeMethod == null)

View file

@ -0,0 +1,53 @@
using Svelto.ECS;
using TechbloxModdingAPI.Blocks;
namespace TechbloxModdingAPI.Utility
{
public static class ApiExtensions
{
/// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded.
/// </summary>
/// <param name="entitiesDB">The entities DB</param>
/// <param name="egid">The EGID to query</param>
/// <typeparam name="T">The component type to query</typeparam>
/// <returns>An optional that contains the result on success or is empty if not found</returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EGID egid)
where T : unmanaged, IEntityComponent
{
return entitiesDB.TryQueryEntitiesAndIndex<T>(egid, out uint index, out var array)
? new OptionalRef<T>(array, index)
: new OptionalRef<T>();
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static OptionalRef<T> QueryEntityOptional<T>(this EntitiesDB entitiesDB, EcsObjectBase obj)
where T : unmanaged, IEntityComponent
{
var opt = QueryEntityOptional<T>(entitiesDB, obj.Id);
return opt ? opt : new OptionalRef<T>(obj);
}
/// <summary>
/// Attempts to query an entity and returns the result or a dummy value that can be modified.
/// </summary>
/// <param name="entitiesDB"></param>
/// <param name="obj"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public static ref T QueryEntityOrDefault<T>(this EntitiesDB entitiesDB, EcsObjectBase obj)
where T : unmanaged, IEntityComponent
{
var opt = QueryEntityOptional<T>(entitiesDB, obj.Id);
if (opt) return ref opt.Get();
if (obj.InitData.Valid) return ref obj.InitData.Initializer(obj.Id).GetOrCreate<T>();
return ref opt.Get(); //Default value
}
}
}

View file

@ -9,31 +9,51 @@ using Svelto.ECS;
namespace TechbloxModdingAPI namespace TechbloxModdingAPI
{ {
public struct OptionalRef<T> where T : unmanaged public ref struct OptionalRef<T> where T : unmanaged, IEntityComponent
{ {
private bool exists; private bool exists;
private NB<T> array; private NB<T> array;
private uint index; private uint index;
private EntityInitializer initializer;
public OptionalRef(NB<T> array, uint index) public OptionalRef(NB<T> array, uint index)
{ {
exists = true; exists = true;
this.array = array; this.array = array;
this.index = index; this.index = index;
initializer = default;
} }
public OptionalRef(ref T value) /// <summary>
/// Wraps the initializer data, if present.
/// </summary>
/// <param name="obj">The object with the initializer</param>
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; array = default;
index = default; index = default;
} }
public ref T Get(T def = default) /// <summary>
/// Returns the value or a default value if empty. Supports objects that are being initialized.
/// </summary>
/// <returns>The value or the default value</returns>
public ref T Get()
{ {
if (exists) if (!exists) return ref CompRefCache<T>.Default;
if (initializer.EGID == EGID.Empty)
return ref array[index]; return ref array[index];
return ref CompRefCache<T>._default; return ref initializer.GetOrCreate<T>();
} }
public bool Exists => exists; public bool Exists => exists;
@ -51,10 +71,10 @@ namespace TechbloxModdingAPI
/// <summary> /// <summary>
/// Creates an instance of a struct T that can be referenced. /// Creates an instance of a struct T that can be referenced.
/// </summary> /// </summary>
/// <typeparam name="T">The struct type to cache</typeparam> /// <typeparam name="TR">The struct type to cache</typeparam>
private struct CompRefCache<T> where T : unmanaged private struct CompRefCache<TR> where TR : unmanaged
{ {
public static T _default; public static TR Default;
} }
} }
} }