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:
parent
78ee3b3bcd
commit
2d99d1d478
5 changed files with 120 additions and 32 deletions
|
@ -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>
|
||||||
|
|
|
@ -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,12 +70,11 @@ 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
|
||||||
|
@ -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);
|
||||||
|
|
|
@ -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)
|
53
TechbloxModdingAPI/Utility/ApiExtensions.cs
Normal file
53
TechbloxModdingAPI/Utility/ApiExtensions.cs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
if (obj.InitData.Valid)
|
||||||
|
{
|
||||||
|
initializer = obj.InitData.Initializer(obj.Id);
|
||||||
exists = true;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue