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
This commit is contained in:
Norbi Peti 2023-03-30 01:17:31 +02:00
parent b3b1e9b9e7
commit 67f32b8810
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
12 changed files with 106 additions and 47 deletions

View file

@ -5,7 +5,6 @@ using System.Reflection;
using DataLoader; using DataLoader;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.Enumerators;
using Unity.Mathematics; using Unity.Mathematics;
using TechbloxModdingAPI.Tests; using TechbloxModdingAPI.Tests;
@ -86,28 +85,44 @@ namespace TechbloxModdingAPI.Blocks
{ {
//Includes specialised block properties //Includes specialised block properties
if (property.SetMethod == null) continue; 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) //(type, default value, predicate or null for equality)
(typeof(long), 3, null), (typeof(long), 3, null),
(typeof(int), 4, null), (typeof(int), 4, null),
(typeof(double), 5.2f, obj => Math.Abs((double) obj - 5.2f) < float.Epsilon), (typeof(double), 5.2f, t => Math.Abs((double) t.Value - (double) t.Default) < float.Epsilon),
(typeof(float), 5.2f, obj => Math.Abs((float) obj - 5.2f) < float.Epsilon), (typeof(float), 5.2f, t => Math.Abs((float) t.Value - (float) t.Default) < float.Epsilon),
(typeof(bool), true, obj => (bool) obj), (typeof(bool), true, t => (bool) t.Value),
(typeof(string), "Test", obj => (string) obj == "Test"), //String equality check (typeof(string), "Test", t => (string) t.Value == "Test"), //String equality check
(typeof(float3), (float3) 2, obj => math.all((float3) obj - 2 < (float3) float.Epsilon)), (typeof(float3), (float3) 20, t => math.all(Float3Compare((float3)t.Value, (float3)t.Default))),
(typeof(BlockColor), new BlockColor(BlockColors.Aqua, 2), null), (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; var propType = property.PropertyType;
if (!propType.IsValueType) continue; 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) foreach (var (type, value, predicate) in testValues)
{ {
if (type.IsAssignableFrom(propType)) if (type.IsAssignableFrom(propType))
{ {
valueToUse = value; valueToUse = value;
predicateToUse = predicate ?? (obj => Equals(obj, value)); predicateToUse = predicate ?? (t => Equals(t.Value, t.Default));
break; break;
} }
} }
@ -116,7 +131,7 @@ namespace TechbloxModdingAPI.Blocks
{ {
var values = propType.GetEnumValues(); var values = propType.GetEnumValues();
valueToUse = values.GetValue(values.Length / 2); valueToUse = values.GetValue(values.Length / 2);
predicateToUse = val => Equals(val, valueToUse); predicateToUse = t => Equals(t.Value, t.Default);
} }
if (valueToUse == null) if (valueToUse == null)
@ -144,7 +159,7 @@ namespace TechbloxModdingAPI.Blocks
continue; continue;
} }
var attr = property.GetCustomAttribute<TestValueAttribute>(); 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}."); Assert.Fail($"Property {block.GetType().Name}.{property.Name} value {got} does not equal {valueToUse} for block {block}.");
yield break; yield break;

View file

@ -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. /// Gets or sets the Engine's On property. May not be saved.
/// </summary> /// </summary>
public bool On public bool On
@ -377,6 +377,6 @@ namespace TechbloxModdingAPI.Blocks
{ {
BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value; BlockEngine.GetBlockInfo<Techblox.EngineBlock.EngineBlockReadonlyComponent>(this).manualToAutoGearCoolOffTime = value;
} }
} }*/
} }
} }

View file

@ -21,6 +21,7 @@ using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using PrefabsID = RobocraftX.Common.PrefabsID; using PrefabsID = RobocraftX.Common.PrefabsID;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
@ -125,7 +126,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
var skew = entitiesDB.QueryEntity<SkewComponent>(id); var skew = entitiesDB.QueryEntity<SkewComponent>(id);
entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix = entitiesDB.QueryEntity<RenderingDataStruct>(id).matrix =
math.mul(float4x4.TRS(pos.position, rot.rotation, scale.scale), skew.skewMatrix); 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) internal void UpdatePrefab(Block block, byte material, bool flipped)
@ -146,8 +147,8 @@ namespace TechbloxModdingAPI.Blocks.Engines
entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId; entitiesDB.QueryEntityOrDefault<GFXPrefabEntityStructGPUI>(block).prefabID = prefabId;
if (block.Exists) if (block.Exists)
{ {
entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id, 30); entitiesDB.PublishEntityChangeDelayed<CubeMaterialStruct>(block.Id);
entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id, 30); entitiesDB.PublishEntityChangeDelayed<GFXPrefabEntityStructGPUI>(block.Id);
ref BuildingActionComponent local = ref BuildingActionComponent local =
ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility ref entitiesDB.QueryEntity<BuildingActionComponent>(BuildingDroneUtility
@ -161,7 +162,7 @@ namespace TechbloxModdingAPI.Blocks.Engines
public void UpdateBlockColor(EGID id) public void UpdateBlockColor(EGID id)
{ {
entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id, 30); entitiesDB.PublishEntityChangeDelayed<ColourParameterEntityStruct>(id);
} }
public bool BlockExists(EGID blockID) public bool BlockExists(EGID blockID)

View file

@ -21,6 +21,7 @@ using Svelto.ECS.Serialization;
using Techblox.Blocks.Connections; using Techblox.Blocks.Connections;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
using Unity.Collections; using Unity.Collections;
using Unity.Mathematics; using Unity.Mathematics;
using UnityEngine; using UnityEngine;

View file

@ -7,6 +7,7 @@ using Unity.Transforms;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {

View file

@ -16,6 +16,7 @@ using Unity.Mathematics;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {

View file

@ -7,6 +7,7 @@ using UnityEngine;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {

View file

@ -6,6 +6,7 @@ using Svelto.ECS;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Blocks.Engines namespace TechbloxModdingAPI.Blocks.Engines
{ {

View file

@ -21,6 +21,7 @@ using Techblox.Character;
using TechbloxModdingAPI.Engines; using TechbloxModdingAPI.Engines;
using TechbloxModdingAPI.Input; using TechbloxModdingAPI.Input;
using TechbloxModdingAPI.Utility; using TechbloxModdingAPI.Utility;
using TechbloxModdingAPI.Utility.ECS;
namespace TechbloxModdingAPI.Players namespace TechbloxModdingAPI.Players
{ {

View file

@ -1,9 +1,7 @@
using System.Collections;
using System.Collections.Generic;
using Svelto.ECS; using Svelto.ECS;
using Svelto.ECS.Hybrid; using Svelto.ECS.Hybrid;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility.ECS
{ {
public static class ManagedApiExtensions public static class ManagedApiExtensions
{ {

View file

@ -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);
}
}
}

View file

@ -1,16 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using Svelto.DataStructures; using Svelto.DataStructures;
using Svelto.ECS; using Svelto.ECS;
using Svelto.Tasks; using Svelto.Tasks;
using Svelto.Tasks.Lean; using Svelto.Tasks.Lean;
using TechbloxModdingAPI.Tasks; using TechbloxModdingAPI.Tasks;
namespace TechbloxModdingAPI.Utility namespace TechbloxModdingAPI.Utility.ECS
{ {
public static class NativeApiExtensions public static partial class NativeApiExtensions
{ {
/// <summary> /// <summary>
/// Attempts to query an entity and returns an optional that contains the result if succeeded. /// 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 return ref opt.Get(); //Default value
} }
private static readonly Dictionary<Type, (int PublishedCount, HashSet<EGID> Changes)> ChangesToPublish = new();
/// <summary> /// <summary>
/// Publishes an entity change, ignoring duplicate publishes and delaying changes as necessary. /// Publishes an entity change, ignoring duplicate publishes and delaying changes as necessary.
/// It will only publish in the next frame. /// It will only publish in the next frame.
/// </summary> /// </summary>
/// <param name="entitiesDB">The entities DB to publish to</param> /// <param name="entitiesDB">The entities DB to publish to</param>
/// <param name="id">The ECS object that got changed</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> /// <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 where T : unmanaged, IEntityComponent
{ {
if (!ChangesToPublish.ContainsKey(typeof(T))) PublishChanges<T>(entitiesDB, id).RunOn(Scheduler.leanRunner);
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);
} }
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 where T : unmanaged, IEntityComponent
{ {
yield return Yield.It; yield return Yield.It;
while (ChangesToPublish[typeof(T)].PublishedCount >= limit) var entityStream = entitiesDB.GetEntityStream<T>();
yield return Yield.It; if (entityStream is null)
if (!entitiesDB._entityStream._streams.TryGetValue(TypeRefWrapper<T>.wrapper, out var result))
yield break; // There is no entity stream for this type yield break; // There is no entity stream for this type
var consumers = (result as EntityStream<T>)?._consumers; var consumers = entityStream.GetConsumers();
if (consumers == null) if (consumers == null)
{ {
Console.WriteLine("Consumers is null"); Console.WriteLine("Consumers is null");
@ -111,21 +100,15 @@ namespace TechbloxModdingAPI.Utility
waitForConsumers = false; waitForConsumers = false;
for (int i = 0; i < consumers.count; i++) 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; if (buffer.Count + 1 <= buffer.Capacity) continue;
waitForConsumers = true; waitForConsumers = true;
Console.WriteLine($"Gonna have to wait for a consumer (capacity: {buffer.Capacity} count: {buffer.Count}");
break; break;
} }
if (waitForConsumers) yield return Yield.It; if (waitForConsumers) yield return Yield.It;
} while (waitForConsumers); } while (waitForConsumers);
entitiesDB.PublishEntityChange<T>(id); 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> /// <summary>