- 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 methodfeature/tb.update
@@ -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<object>)[] | |||
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<object> 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<TestValueAttribute>(); | |||
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; | |||
@@ -23,7 +23,7 @@ namespace TechbloxModdingAPI.Blocks | |||
{ | |||
} | |||
/// <summary> | |||
/*/// <summary> - TODO: Internal struct access | |||
/// Gets or sets the Engine's On property. May not be saved. | |||
/// </summary> | |||
public bool On | |||
@@ -377,6 +377,6 @@ namespace TechbloxModdingAPI.Blocks | |||
{ | |||
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value; | |||
} | |||
} | |||
}*/ | |||
} | |||
} |
@@ -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<SkewComponent>(id); | |||
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix = | |||
math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix); | |||
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(id, 30); // Signal a prefab change so it updates the render buffers | |||
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(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<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId; | |||
if (block.Exists) | |||
{ | |||
entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id, 30); | |||
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id, 30); | |||
entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id); | |||
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id); | |||
ref BuildingActionComponent local = | |||
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility | |||
@@ -161,7 +162,7 @@ namespace TechbloxModdingAPI.Blocks.Engines | |||
public void UpdateBlockColor(EGID id) | |||
{ | |||
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id, 30); | |||
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id); | |||
} | |||
public bool BlockExists(EGID blockID) | |||
@@ -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; | |||
@@ -7,6 +7,7 @@ using Unity.Transforms; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using TechbloxModdingAPI.Utility.ECS; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
@@ -16,6 +16,7 @@ using Unity.Mathematics; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using TechbloxModdingAPI.Utility.ECS; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
@@ -7,6 +7,7 @@ using UnityEngine; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using TechbloxModdingAPI.Utility.ECS; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
@@ -6,6 +6,7 @@ using Svelto.ECS; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Utility; | |||
using TechbloxModdingAPI.Utility.ECS; | |||
namespace TechbloxModdingAPI.Blocks.Engines | |||
{ | |||
@@ -21,6 +21,7 @@ using Techblox.Character; | |||
using TechbloxModdingAPI.Engines; | |||
using TechbloxModdingAPI.Input; | |||
using TechbloxModdingAPI.Utility; | |||
using TechbloxModdingAPI.Utility.ECS; | |||
namespace TechbloxModdingAPI.Players | |||
{ | |||
@@ -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 | |||
{ |
@@ -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<T> 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<MethodInfo> EntityStream = | |||
new(() => AccessTools.PropertyGetter(typeof(EntitiesDB), "_entityStream")); | |||
public static readonly Lazy<FieldInfo> Streams = new(() => | |||
AccessTools.Field(EntityStream.Value.ReturnType, "_streams")); | |||
public static readonly Lazy<FieldInfo> Consumers = new(() => | |||
AccessTools.Field(typeof(EntityStream<T>), "_consumers")); | |||
public static readonly Lazy<MethodInfo> TryGetValue = | |||
new(AccessTools.Method(Streams.Value.FieldType, "TryGetValue")); | |||
public static readonly Lazy<FieldInfo> RingBuffer = | |||
new(() => AccessTools.Field(typeof(Consumer<T>), "_ringBuffer")); | |||
} | |||
private static EntityStream<T> GetEntityStream<T>(this EntitiesDB entitiesDB) where T : unmanaged, IEntityComponent | |||
{ | |||
// EntitiesStreams (internal) | |||
var entitiesStreams = EntitiesDBHelper<T>.EntityStream.Value.Invoke(entitiesDB, Array.Empty<object>()); | |||
// FasterDictionary<RefWrapperType, ITypeSafeStream> (interface is internal) | |||
var streams = EntitiesDBHelper<T>.Streams.Value.GetValue(entitiesStreams); | |||
var parameters = new object[] { TypeRefWrapper<T>.wrapper, null }; | |||
var success = EntitiesDBHelper<T>.TryGetValue.Value.Invoke(streams, parameters); | |||
if (!(bool)success) | |||
return null; // There is no entity stream for this type | |||
return (EntityStream<T>)parameters[1]; | |||
} | |||
private static ThreadSafeFasterList<Consumer<T>> GetConsumers<T>(this EntityStream<T> stream) where T : unmanaged, IEntityComponent | |||
{ | |||
return (ThreadSafeFasterList<Consumer<T>>)EntitiesDBHelper<T>.Consumers.Value.GetValue(stream); | |||
} | |||
private static RingBuffer<(T, EGID)> GetRingBuffer<T>(this Consumer<T> consumer) where T : unmanaged, IEntityComponent | |||
{ | |||
return (RingBuffer<(T, EGID)>)EntitiesDBHelper<T>.RingBuffer.Value.GetValue(consumer); | |||
} | |||
} | |||
} |
@@ -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 | |||
{ | |||
/// <summary> | |||
/// 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<Type, (int PublishedCount, HashSet<EGID> Changes)> ChangesToPublish = new(); | |||
/// <summary> | |||
/// Publishes an entity change, ignoring duplicate publishes and delaying changes as necessary. | |||
/// It will only publish in the next frame. | |||
/// </summary> | |||
/// <param name="entitiesDB">The entities DB to publish to</param> | |||
/// <param name="id">The ECS object that got changed</param> | |||
/// <param name="limit">Limits how many changes to publish - should be no more than the consumers' capacity that process this component</param> | |||
/// <typeparam name="T">The component that changed</typeparam> | |||
public static void PublishEntityChangeDelayed<T>(this EntitiesDB entitiesDB, EGID id, int limit = 80) | |||
public static void PublishEntityChangeDelayed<T>(this EntitiesDB entitiesDB, EGID id) | |||
where T : unmanaged, IEntityComponent | |||
{ | |||
if (!ChangesToPublish.ContainsKey(typeof(T))) | |||
ChangesToPublish.Add(typeof(T), (0, new HashSet<EGID>())); | |||
var changes = ChangesToPublish[typeof(T)].Changes; | |||
if (changes.Contains(id)) return; | |||
changes.Add(id); | |||
PublishChanges<T>(entitiesDB, id, limit).RunOn(Scheduler.leanRunner); | |||
PublishChanges<T>(entitiesDB, id).RunOn(Scheduler.leanRunner); | |||
} | |||
private static IEnumerator<TaskContract> PublishChanges<T>(EntitiesDB entitiesDB, EGID id, int limit) | |||
private static IEnumerator<TaskContract> PublishChanges<T>(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<T>.wrapper, out var result)) | |||
var entityStream = entitiesDB.GetEntityStream<T>(); | |||
if (entityStream is null) | |||
yield break; // There is no entity stream for this type | |||
var consumers = (result as EntityStream<T>)?._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<T>(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); | |||
} | |||
/// <summary> |