TechbloxModdingAPI/TechbloxModdingAPI/EcsObjectBase.cs
Norbi Peti d27bcee8d5 Update to Techblox 2022.01.25.15.52
- Fixed compilation errors
- Fixed patching errors and added missing anti-cheat patch
- Added check to verify that the init data has been removed from blocks once they are placed in game
- Removed block place event deduplication as it seems to be not needed anymore
- Fixed async tests not properly running
- Added Player.State
- Attempted to fix seat entering/leaving (we can only send inputs in client code)
- Fixed the weak dictionary ContainsKey returning true even if the item is no longer there
2022-01-30 04:32:10 +01:00

128 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using Svelto.DataStructures;
using Svelto.ECS;
using Svelto.ECS.Internal;
using TechbloxModdingAPI.Utility;
namespace TechbloxModdingAPI
{
public abstract class EcsObjectBase
{
public EGID Id { get; }
private static readonly Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>> _instances =
new Dictionary<Type, WeakDictionary<EGID, EcsObjectBase>>();
private static readonly WeakDictionary<EGID, EcsObjectBase> _noInstance =
new WeakDictionary<EGID, EcsObjectBase>();
internal static WeakDictionary<EGID, EcsObjectBase> GetInstances(Type type)
{
return _instances.TryGetValue(type, out var dict) ? dict : null;
}
/// <summary>
/// Returns a cached instance if there's an actively used instance of the object already.
/// Objects still get garbage collected and then they will be removed from the cache.
/// </summary>
/// <param name="egid">The EGID of the entity</param>
/// <param name="constructor">The constructor to construct the object</param>
/// <typeparam name="T">The object type</typeparam>
/// <returns></returns>
internal static T GetInstance<T>(EGID egid, Func<EGID, T> constructor, Type type = null) where T : EcsObjectBase
{
var instances = GetInstances(type ?? typeof(T));
if (instances == null || !instances.TryGetValue(egid, out var instance))
return constructor(egid); // It will be added by the constructor
return (T)instance;
}
protected EcsObjectBase(EGID id)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new WeakDictionary<EGID, EcsObjectBase>();
_instances.Add(GetType(), dict);
}
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
Id = id;
}
protected EcsObjectBase(Func<EcsObjectBase, EGID> initializer)
{
if (!_instances.TryGetValue(GetType(), out var dict))
{
dict = new WeakDictionary<EGID, EcsObjectBase>();
_instances.Add(GetType(), dict);
}
var id = initializer(this);
if (!dict.ContainsKey(id)) // Multiple instances may be created
dict.Add(id, this);
else
{
Logging.MetaDebugLog($"An object of this type and ID is already stored: {GetType()} - {id}");
Logging.MetaDebugLog(this);
Logging.MetaDebugLog(dict[id]);
}
Id = id;
}
#region ECS initializer stuff
protected internal EcsInitData InitData;
/// <summary>
/// Holds information needed to construct a component initializer
/// </summary>
protected internal struct EcsInitData
{
private FasterDictionary<RefWrapperType, ITypeSafeDictionary> 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;
}
private delegate FasterDictionary<RefWrapperType, ITypeSafeDictionary> GetInitGroupFunc(
EntityInitializer initializer);
/// <summary>
/// Accesses the group field of the initializer
/// </summary>
private static GetInitGroupFunc GetInitGroup = CreateAccessor<GetInitGroupFunc>("_group");
//https://stackoverflow.com/questions/55878525/unit-testing-ref-structs-with-private-fields-via-reflection
private static TDelegate CreateAccessor<TDelegate>(string memberName) where TDelegate : Delegate
{
var invokeMethod = typeof(TDelegate).GetMethod("Invoke");
if (invokeMethod == null)
throw new InvalidOperationException($"{typeof(TDelegate)} signature could not be determined.");
var delegateParameters = invokeMethod.GetParameters();
if (delegateParameters.Length != 1)
throw new InvalidOperationException("Delegate must have a single parameter.");
var paramType = delegateParameters[0].ParameterType;
var objParam = Expression.Parameter(paramType, "obj");
var memberExpr = Expression.PropertyOrField(objParam, memberName);
Expression returnExpr = memberExpr;
if (invokeMethod.ReturnType != memberExpr.Type)
returnExpr = Expression.ConvertChecked(memberExpr, invokeMethod.ReturnType);
var lambda =
Expression.Lambda<TDelegate>(returnExpr, $"Access{paramType.Name}_{memberName}", new[] { objParam });
return lambda.Compile();
}
#endregion
}
}