Browse Source

UPM package version 3.5.1

tags/3.5.1
GitHub 4 months ago
parent
commit
e5fe088791
24 changed files with 437 additions and 203 deletions
  1. +13
    -0
      CHANGELOG.md
  2. +1
    -0
      Core/CheckEntityUtilities.cs
  3. +6
    -2
      Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs
  4. +11
    -1
      Core/EnginesRoot.Engines.cs
  5. +5
    -3
      Core/EnginesRoot.Entities.cs
  6. +9
    -2
      Core/EntitiesOperations.cs
  7. +29
    -5
      Core/EntitySubmissionScheduler.cs
  8. +3
    -3
      Core/Filters/EntityFilterCollection.cs
  9. +8
    -0
      Core/Groups/AbstractEnginesGroupSwapSupport.meta
  10. +117
    -0
      Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveBuildGroupExtensions.cs
  11. +1
    -1
      Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveBuildGroupExtensions.cs.meta
  12. +60
    -0
      Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveGroupStructExtensions.cs
  13. +1
    -1
      Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveGroupStructExtensions.cs.meta
  14. +7
    -0
      Core/Groups/EntitiesDB.FindGroups.cs
  15. +3
    -5
      Core/Groups/ExclusiveGroup.cs
  16. +18
    -13
      Core/Groups/GroupHashMap.cs
  17. +1
    -0
      Core/IEngine.cs
  18. +5
    -30
      Core/SimpleEntitiesSubmissionScheduler.cs
  19. +2
    -6
      Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs
  20. +2
    -2
      Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs
  21. +0
    -35
      Extensions/Unity/MonoScheduler.cs
  22. +0
    -52
      Extensions/Unity/UnityEntitiesSubmissionScheduler.cs
  23. +133
    -40
      README.md
  24. +2
    -2
      package.json

+ 13
- 0
CHANGELOG.md View File

@@ -1,6 +1,19 @@
# Svelto.ECS Changelog
All notable changes to this project will be documented in this file. Changes are listed in random order of importance.

## [3.5.1] - 01-2024

* Remove UnityEntitySubmissionScheduler, it was never needed, the user can use the standard EntitySubmissionScheduler and tick it manually
* Dropped the idea to specialise EntitiesSubmissionScheduler. In hindsight it was never necessary.
* Added better support for range exclusive groups, now they are correctly registered in the group hash map
* Removed announg Group compound/tag {type} is not sealed warning
* Merged Cuyi's workaround to be able to query compound groups in abstract engine. Never had the time to implement a better solution
* It is now possible again to add an entity multiple times inside a filter (it will be overriden)
* Fixed issue https://github.com/sebas77/Svelto.ECS/issues/123
* Fixed issue https://github.com/sebas77/Svelto.ECS/issues/122
* Fixed issue https://github.com/sebas77/Svelto.ECS/issues/121
* AddEngine now adds engines contained in a GroupEngine to the EnginesGroup optionally~~~~

## [3.5.0] - 09-2023

* Introduced Serialization namespace for the serialization code


+ 1
- 0
Core/CheckEntityUtilities.cs View File

@@ -76,6 +76,7 @@ namespace Svelto.ECS
bool isAllowed = false;
if (_multipleOperationOnSameEGIDChecker.TryGetValue(egid, out var operationType) == true)
{
//remove supersedes swap and remove operations, this means remove is allowed if the previous operation was swap or remove on the same submission
isAllowed = operationType == OperationType.Remove || operationType == OperationType.SwapFrom;

if (isAllowed)


+ 6
- 2
Core/EnginesRoot.DoubleBufferedEntitiesToAdd.cs View File

@@ -152,13 +152,17 @@ namespace Svelto.ECS
void PreallocateDictionaries
(FasterDictionary<ExclusiveGroupStruct, FasterDictionary<ComponentID, ITypeSafeDictionary>> dic)
{
//get the set of entities in the group ID
var group = dic.GetOrAdd(
groupID, () => new FasterDictionary<ComponentID, ITypeSafeDictionary>());

//for each component of the entities in the group
foreach (var componentBuilder in entityComponentsToBuild)
{
var safeDictionary = group.GetOrAdd(componentBuilder.getComponentID, () => componentBuilder.CreateDictionary(numberOfEntities));
componentBuilder.Preallocate(safeDictionary, numberOfEntities);
//get the dictionary of entities for the component type
var components = group.GetOrAdd(componentBuilder.getComponentID, () => componentBuilder.CreateDictionary(numberOfEntities));
componentBuilder.Preallocate(components, numberOfEntities);
}
}



+ 11
- 1
Core/EnginesRoot.Engines.cs View File

@@ -92,6 +92,11 @@ namespace Svelto.ECS
#endif
}

/// <summary>
///Ready is a callback that can be used to signal that an engine is ready to be used because the entitiesDB is now available
///usually engines are ready to be used when they are added to the enginesRoot, but in some special cases, it is possible to
///wait for the user input to signal that engines are ready to be used
/// </summary>
protected EnginesRoot(EntitiesSubmissionScheduler entitiesComponentScheduler,
EnginesReadyOption enginesWaitForReady): this(entitiesComponentScheduler)
{
@@ -116,7 +121,7 @@ namespace Svelto.ECS
return _isDisposed == false;
}
public void AddEngine(IEngine engine)
public void AddEngine(IEngine engine, bool addSubEngines = true)
{
var type = engine.GetType();
var refWrapper = new RefWrapperType(type);
@@ -174,6 +179,7 @@ namespace Svelto.ECS
if (engine is IReactOnSubmissionStarted submissionEngineStarted)
_reactiveEnginesSubmissionStarted.Add(submissionEngineStarted);
if (addSubEngines)
if (engine is IGroupEngine stepGroupEngine)
foreach (var stepEngine in stepGroupEngine.engines)
AddEngine(stepEngine);
@@ -187,6 +193,7 @@ namespace Svelto.ECS
if (engine is IQueryingEntitiesEngine queryableEntityComponentEngine)
queryableEntityComponentEngine.entitiesDB = _entitiesDB;

//Ready is a callback that can be used to signal that the engine is ready to be used because the entitiesDB is now available
if (_enginesWaitForReady == EnginesReadyOption.ReadyAsAdded && engine is IGetReadyEngine getReadyEngine)
getReadyEngine.Ready();
}
@@ -432,6 +439,9 @@ namespace Svelto.ECS
bool _isDisposed;
}

//Ready is a callback that can be used to signal that an engine is ready to be used because the entitiesDB is now available
//usually engines are ready to be used when they are added to the enginesRoot, but in some special cases, it is possible to
//wait for the user input to signal that the engine is ready to be used
public enum EnginesReadyOption
{
ReadyAsAdded,


+ 5
- 3
Core/EnginesRoot.Entities.cs View File

@@ -90,14 +90,16 @@ namespace Svelto.ECS
var entityComponentBuilder = entityComponentsToBuild[index];
var entityComponentType = entityComponentBuilder.getComponentID;

var dbList = group.GetOrAdd(entityComponentType, () => entityComponentBuilder.CreateDictionary(size));
entityComponentBuilder.Preallocate(dbList, size);
var components = group.GetOrAdd(entityComponentType, () => entityComponentBuilder.CreateDictionary(size));
if (components.count != 0)
throw new ECSException("Entity already created in this group, cannot preallocate");
entityComponentBuilder.Preallocate(components, size);

if (_groupsPerEntity.TryGetValue(entityComponentType, out var groupedGroup) == false)
groupedGroup = _groupsPerEntity[entityComponentType] =
new FasterDictionary<ExclusiveGroupStruct, ITypeSafeDictionary>();

groupedGroup[groupID] = dbList;
groupedGroup[groupID] = components;
}
}



+ 9
- 2
Core/EntitiesOperations.cs View File

@@ -33,8 +33,15 @@ namespace Svelto.ECS

public void QueueRemoveOperation(EGID fromEgid, IComponentBuilder[] componentBuilders, string caller)
{
// Check if the entity is already queued for removal
if (_thisSubmissionInfo._entitiesRemoved.Contains(fromEgid))
{
// If it is, skip the rest of the function
return;
}
_thisSubmissionInfo._entitiesRemoved.Add(fromEgid);
RevertSwapOperation(fromEgid);
RevertSwapOperationIfPreviouslyQueued(fromEgid);

//todo: limit the number of dictionaries that can be cached
//recycle or create dictionaries of components per group
@@ -49,7 +56,7 @@ namespace Svelto.ECS
.Add((fromEgid.entityID, caller));
}

void RevertSwapOperation(EGID fromEgid)
void RevertSwapOperationIfPreviouslyQueued(EGID fromEgid)
{
if (_thisSubmissionInfo._entitiesSwapped.Remove(fromEgid, out (EGID fromEgid, EGID toEgid) val)) //Remove supersedes swap, check comment in IEntityFunctions.cs
{


+ 29
- 5
Core/EntitySubmissionScheduler.cs View File

@@ -1,14 +1,38 @@
namespace Svelto.ECS.Schedulers
{
public abstract class EntitiesSubmissionScheduler
public class EntitiesSubmissionScheduler
{
protected internal abstract EnginesRoot.EntitiesSubmitter onTick { set; }

public abstract void Dispose();

public bool paused { get; set; }
public uint iteration { get; protected internal set; }

internal bool isRunning;
protected internal EnginesRoot.EntitiesSubmitter onTick
{
set
{
DBC.ECS.Check.Require(_entitiesSubmitter == null, "a scheduler can be exclusively used by one enginesRoot only");

_entitiesSubmitter = value;
}
}

public void Dispose() { }

public void SubmitEntities()
{
try
{
_entitiesSubmitter.Value.SubmitEntities();
}
catch
{
paused = true;
throw;
}
}

EnginesRoot.EntitiesSubmitter? _entitiesSubmitter;
}
}

+ 3
- 3
Core/Filters/EntityFilterCollection.cs View File

@@ -217,12 +217,12 @@ namespace Svelto.ECS
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(uint entityId, uint indexInComponentArray)
public bool Add(uint entityId, uint indexInComponentArray)
{
//TODO: check what happens (unit test) if adding the same entityID twice
//it's a TryAdd because there is no reason to not replace the index if the entity is already there
//TODO: when sentinels are finished, we need to add AsWriter here
//cannot write in parallel
_entityIDToDenseIndex.Add(entityId, indexInComponentArray);
return _entityIDToDenseIndex.TryAdd(entityId, indexInComponentArray, out _);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]


+ 8
- 0
Core/Groups/AbstractEnginesGroupSwapSupport.meta View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 76469379bb5731bba75992adba9db258
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

+ 117
- 0
Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveBuildGroupExtensions.cs View File

@@ -0,0 +1,117 @@
using System;
using Svelto.Common;
using Svelto.DataStructures;

namespace Svelto.ECS
{
//Hi! I've been following your discussion on discord and have just a few questions on how to use this snippet properly:
//
//Did I get the idea right: this snippet allows us to swap group tags (from current to target) even if target combination of tags not declared in groups class (like GameGroups.cs in Doofuses iteration 3 example)? If so:
//On discord you wrote:
//The specialized engines define the swaps like this:
//GroupCompound<SHIP, AI>.BuildGroup.SetTagSwap<SHIP, SUNK_SHIP>(GroupCompound<SUNK_SHIP, AI>.BuildGroup);
//GroupCompound<SHIP, PLAYER>.BuildGroup.SetTagSwap<SHIP, SUNK_SHIP>(GroupCompound<SUNK_SHIP, PLAYER>.BuildGroup);
//
//And any engine can do this:
//var targetGroup = egid.groupID.GetSwapTag<SUNK_SHIP, SHIP>();
//_functions.SwapEntityGroup<ShipEntityDescriptor>(egid, targetGroup);
//
//by specialized / any did you mean not abstract / abstract or something else?
//
//When _removeTransitions, _addTransitions, _swapTransitions should be filled?
//@jlreymendez
//Author
//jlreymendez commented on Aug 10, 2020
//Hey!
//
//1- Yeah, it would allow you to swap to targets that are not directly declared in a group class. In reality you are declaring them when you declare the SetTagSwap, SetTagAddition and SetTagRemoval.
//
//2- Yes, correct by specialized I mean something not abstract, an engine that knows all the tags that apply to an entity. Thus it would allow you to create a comprehensive list of the swaps like you see in that code. Remember that when you do SetTagSwap<SHIP, SUNK_SHIP> the reverse will also be declared automatically.
//
//This is the latest from that code you shared:
//
//
// // Register possible transitions.
// GroupCompound<SHIP, AI, PIRATE>.BuildGroup.SetTagSwap<SHIP, SUNK_SHIP>(GroupCompound<SUNK_SHIP, AI, PIRATE>.BuildGroup);
// GroupCompound<SHIP, AI, MERCHANT>.BuildGroup.SetTagSwap<SHIP, SUNK_SHIP>(GroupCompound<SUNK_SHIP, AI, MERCHANT>.BuildGroup);
// GroupCompound<SHIP, AI, NORMAL>.BuildGroup.SetTagSwap<SHIP, SUNK_SHIP>(GroupCompound<SUNK_SHIP, AI, NORMAL>.BuildGroup);
//
// GroupCompound<SHIP, PLAYER>.BuildGroup.SetTagSwap<SHIP, SUNK_SHIP>(GroupCompound<SUNK_SHIP, PLAYER>.BuildGroup);
//
//3- You would fill them at initialization time, I did it in the Ready() of the engine before entering the game loop. But you could do it in the constructor of the engine. Wherever you find it appropriate before you need to start swapping or changing tags.
public static class ExclusiveBuildGroupExtensions
{
internal static FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>>
_removeTransitions =
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>>();

internal static FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>>
_addTransitions =
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>>();

internal static FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>>
_swapTransitions =
new FasterDictionary<ExclusiveGroupStruct, FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>>();

public static void SetTagAddition<T>
(this ExclusiveBuildGroup group, ExclusiveBuildGroup target, bool setReverse = true) where T : GroupTag<T>
{
if (_addTransitions.TryGetValue(@group, out var transitions) == false)
{
transitions = new FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>();
_addTransitions[@group] = transitions;
}

var type = new RefWrapper<Type>(typeof(T));
transitions[type] = target;

if (setReverse)
{
SetTagRemoval<T>(target, group, false);
}
}

public static void SetTagRemoval<T>
(this ExclusiveBuildGroup group, ExclusiveBuildGroup target, bool setReverse = true) where T : GroupTag<T>
{
if (_removeTransitions.TryGetValue(@group, out var transitions) == false)
{
transitions = new FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>();
_removeTransitions[@group] = transitions;
}

var type = new RefWrapper<Type>(typeof(T));
transitions[type] = target;

if (setReverse)
{
SetTagAddition<T>(target, group, false);
}
}

public static void SetTagSwap<TRemove, TAdd>
(this ExclusiveBuildGroup group, ExclusiveBuildGroup target, bool setReverse = true)
where TRemove : GroupTag<TRemove> where TAdd : GroupTag<TAdd>
{
if (_swapTransitions.TryGetValue(@group, out var transitions) == false)
{
transitions = new FasterDictionary<RefWrapper<Type>, ExclusiveBuildGroup>();
_swapTransitions[group] = transitions;
}

var type = new RefWrapper<Type>(typeof(TAdd));
transitions[type] = target;

// To avoid needing to check if the group already has the tag when swapping (prevent ecs exceptions).
// The current groups adds the removed tag pointing to itself.
type = new RefWrapper<Type>(typeof(TRemove));
transitions[type] = group;

if (setReverse)
{
SetTagSwap<TAdd, TRemove>(target, group, false);
}
}
}
}

Extensions/Unity/MonoScheduler.cs.meta → Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveBuildGroupExtensions.cs.meta View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: ba3a7db4cabf3537b4fb9388efd1ab6f
guid: fc967b14584731f09ece8cfa342f6d54
MonoImporter:
externalObjects: {}
serializedVersion: 2

+ 60
- 0
Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveGroupStructExtensions.cs View File

@@ -0,0 +1,60 @@
using System;
using Svelto.DataStructures;

namespace Svelto.ECS
{
public static class ExclusiveGroupStructExtensions
{
public static ExclusiveBuildGroup RemoveTag<T>(this ExclusiveGroupStruct group) where T : GroupTag<T>
{
if (ExclusiveBuildGroupExtensions._removeTransitions.TryGetValue(@group, out var transitions))
{
var type = new RefWrapper<Type>(typeof(T));
if (transitions.TryGetValue(type, out var result))
{
return result;
}
}

throw new ECSException("No remove transition found for type "
.FastConcat(typeof(T).ToString())
.FastConcat(" in group ").FastConcat(@group.ToString())
);
}

public static ExclusiveBuildGroup AddTag<T>(this ExclusiveGroupStruct group) where T : GroupTag<T>
{
if (ExclusiveBuildGroupExtensions._addTransitions.TryGetValue(group, out var transitions))
{
var type = new RefWrapper<Type>(typeof(T));
if (transitions.TryGetValue(type, out var result))
{
return result;
}
}

throw new ECSException("No add transition found for type "
.FastConcat(typeof(T).ToString())
.FastConcat(" in group ").FastConcat(@group.ToString())
);
}

public static ExclusiveBuildGroup SwapTag<TTarget>(this ExclusiveGroupStruct group)
where TTarget : GroupTag<TTarget>
{
var type = new RefWrapper<Type>(typeof(TTarget));
if (ExclusiveBuildGroupExtensions._swapTransitions.TryGetValue(@group, out var transitions))
{
if (transitions.TryGetValue(type, out var result))
{
return result;
}
}

throw new ECSException("No swap transition found for type "
.FastConcat(typeof(TTarget).ToString())
.FastConcat(" in group ").FastConcat(@group.ToString())
);
}
}
}

Extensions/Unity/UnityEntitiesSubmissionScheduler.cs.meta → Core/Groups/AbstractEnginesGroupSwapSupport/ExclusiveGroupStructExtensions.cs.meta View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: b19cf0516b0e39dea2b35c127e098c78
guid: ff0a2df6bea437f39e52294e3f504527
MonoImporter:
externalObjects: {}
serializedVersion: 2

+ 7
- 0
Core/Groups/EntitiesDB.FindGroups.cs View File

@@ -7,6 +7,10 @@ namespace Svelto.ECS
{
public partial class EntitiesDB
{
/// <summary>
/// Return all the groups where the entity component is found. It's a linear operation, but usually the number of groups are very low
/// By default Groups marked as disabled are not returned, however this can be overridden using the ignoreDisabledBit parameter
/// </summary>
public LocalFasterReadOnlyList<ExclusiveGroupStruct> FindGroups<T1>(bool ignoreDisabledBit = false) where T1 : struct, _IInternalEntityComponent
{
FasterList<ExclusiveGroupStruct> result = localgroups.Value.groupArray;
@@ -123,6 +127,7 @@ namespace Svelto.ECS
if (localGroups.count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
groupData = localArray[++startIndex % 3];
localGroups.Intersect(groupData);

@@ -180,11 +185,13 @@ namespace Svelto.ECS
if (localGroups.count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
groupData = localArray[++startIndex & 3];
localGroups.Intersect(groupData);
if (localGroups.count == 0)
return new LocalFasterReadOnlyList<ExclusiveGroupStruct>(
FasterReadOnlyList<ExclusiveGroupStruct>.DefaultEmptyList);
groupData = localArray[++startIndex & 3];
localGroups.Intersect(groupData);



+ 3
- 5
Core/Groups/ExclusiveGroup.cs View File

@@ -42,9 +42,8 @@ namespace Svelto.ECS
public ExclusiveGroup(ushort range)
{
_group = ExclusiveGroupStruct.GenerateWithRange(range);
#if DEBUG && !PROFILE_SVELTO
_range = range;
#endif
}
public ExclusiveGroup(ushort range, ExclusiveGroupBitmask bitmask)
@@ -90,9 +89,8 @@ namespace Svelto.ECS
static readonly Dictionary<string, ExclusiveGroupStruct> _knownGroups =
new Dictionary<string, ExclusiveGroupStruct>();

#if DEBUG && !PROFILE_SVELTO
readonly ushort _range;
#endif
internal readonly ushort _range;

readonly ExclusiveGroupStruct _group;
}
}

+ 18
- 13
Core/Groups/GroupHashMap.cs View File

@@ -52,10 +52,12 @@ namespace Svelto.ECS
{
uint groupIDAndBitMask;

int range = 0;
if (typeOfExclusiveGroup.IsAssignableFrom(field.FieldType))
{
var group = (ExclusiveGroup)field.GetValue(null);
groupIDAndBitMask = ((ExclusiveGroupStruct)@group).ToIDAndBitmask();
range = @group._range;
}
else if (typeOfExclusiveGroupStruct.IsAssignableFrom(field.FieldType))
{
@@ -73,13 +75,24 @@ namespace Svelto.ECS
var groupID = groupIDAndBitMask & 0xFFFFFF;
ExclusiveGroupStruct group = new ExclusiveGroupStruct(groupID, bitMask);
#if DEBUG && !PROFILE_SVELTO
if (GroupNamesMap.idToName.ContainsKey(@group) == false)
GroupNamesMap.idToName[@group] =
$"{type.FullName}.{field.Name} {@group.id})";
if (GroupNamesMap.idToName.ContainsKey(@group) == false)
GroupNamesMap.idToName[@group] =
$"{type.FullName}.{field.Name} id: {@group.id}";
#endif
//The hashname is independent from the actual group ID. this is fundamental because it is want
//guarantees the hash to be the same across different machines
RegisterGroup(@group, $"{type.FullName}.{field.Name}");

for (uint i = 1; i < range; i++)
{
var exclusiveGroupStruct = group + i;
#if DEBUG && !PROFILE_SVELTO
if (GroupNamesMap.idToName.ContainsKey(exclusiveGroupStruct) == false)
GroupNamesMap.idToName[exclusiveGroupStruct] =
$"{type.FullName}.{field.Name} id: {@group.id + i}";
#endif
RegisterGroup(exclusiveGroupStruct, $"{type.FullName}.{field.Name} id: {@group.id + i}");
}
}
}
}
@@ -102,15 +115,7 @@ namespace Svelto.ECS
// Check if the current type has a static constructor
// type.TypeInitializer.Invoke(null, null); //calling Invoke will force the static constructor to be called even if already called, this is a problem because GroupTag and Compound throw an exception if called multiple times
RuntimeHelpers.RunClassConstructor(type.TypeHandle); //this will call the static constructor only once
#if DEBUG && !PROFILE_SVELTO
if (type.GetInterfaces().Contains(type) == false)
{
if (type.IsSealed == false)
Svelto.Console.LogWarning(
$"Group compound/tag {type} is not sealed. GroupCompounds and Tags cannot be inherited, consider marking it sealed");
}
#endif

// Recursively check the base types
Type baseType = type.BaseType;
if (baseType != null && baseType != typeof(object)) //second if means we got the the end of the hierarchy
@@ -121,7 +126,7 @@ namespace Svelto.ECS
}

/// <summary>
/// The hashname is independent from the actual group ID. this is fundamental because it is want
/// The hashname is independent from the actual group ID. this is fundamental because it
/// guarantees the hash to be the same across different machines
/// </summary>
/// <param name="exclusiveGroupStruct"></param>


+ 1
- 0
Core/IEngine.cs View File

@@ -60,6 +60,7 @@ namespace Svelto.ECS

public interface IGetReadyEngine : IEngine
{
//Ready is a callback that can be used to signal that the engine is ready to be used because the entitiesDB is now available
void Ready();
}



+ 5
- 30
Core/SimpleEntitiesSubmissionScheduler.cs View File

@@ -1,33 +1,8 @@
namespace Svelto.ECS.Schedulers
using System;

namespace Svelto.ECS.Schedulers
{
[Obsolete("SimpleEntitiesSubmissionScheduler is obsolete, use EntitiesSubmissionScheduler instead")]
public sealed class SimpleEntitiesSubmissionScheduler : EntitiesSubmissionScheduler
{
protected internal override EnginesRoot.EntitiesSubmitter onTick
{
set
{
DBC.ECS.Check.Require(_entitiesSubmitter == null, "a scheduler can be exclusively used by one enginesRoot only");

_entitiesSubmitter = value;
}
}

public override void Dispose() { }

public void SubmitEntities()
{
try
{
_entitiesSubmitter.Value.SubmitEntities();
}
catch
{
paused = true;
throw;
}
}

EnginesRoot.EntitiesSubmitter? _entitiesSubmitter;
}
{ }
}

+ 2
- 6
Extensions/Unity/DOTS/UECS/SveltoOnDOTSEnginesGroup.cs View File

@@ -29,11 +29,7 @@ namespace Svelto.ECS.SveltoOnDOTS
{
public SveltoOnDOTSEnginesGroup(EnginesRoot enginesRoot)
{
DBC.ECS.Check.Require(
enginesRoot.scheduler is SimpleEntitiesSubmissionScheduler
, "The Engines root must use a EntitiesSubmissionScheduler scheduler implementation");

CreateUnityECSWorldForSvelto(enginesRoot.scheduler as SimpleEntitiesSubmissionScheduler, enginesRoot);
CreateUnityECSWorldForSvelto(enginesRoot.scheduler, enginesRoot);
}
/// <summary>
@@ -121,7 +117,7 @@ namespace Svelto.ECS.SveltoOnDOTS
world.Dispose();
}

void CreateUnityECSWorldForSvelto(SimpleEntitiesSubmissionScheduler scheduler, EnginesRoot enginesRoot)
void CreateUnityECSWorldForSvelto(EntitiesSubmissionScheduler scheduler, EnginesRoot enginesRoot)
{
world = new World("Svelto<>DOTS world");



+ 2
- 2
Extensions/Unity/DOTS/UECS/SveltoOnDOTSEntitiesSubmissionGroup.cs View File

@@ -26,7 +26,7 @@ namespace Svelto.ECS.SveltoOnDOTS
[DisableAutoCreation]
public sealed partial class SveltoOnDOTSEntitiesSubmissionGroup: SystemBase, IQueryingEntitiesEngine, ISveltoOnDOTSSubmission
{
public SveltoOnDOTSEntitiesSubmissionGroup(SimpleEntitiesSubmissionScheduler submissionScheduler)
public SveltoOnDOTSEntitiesSubmissionGroup(EntitiesSubmissionScheduler submissionScheduler)
{
_submissionScheduler = submissionScheduler;
_structuralEngines = new FasterList<ISveltoOnDOTSStructuralEngine>();
@@ -106,7 +106,7 @@ namespace Svelto.ECS.SveltoOnDOTS
}

readonly FasterList<ISveltoOnDOTSStructuralEngine> _structuralEngines;
readonly SimpleEntitiesSubmissionScheduler _submissionScheduler;
readonly EntitiesSubmissionScheduler _submissionScheduler;
DOTSOperationsForSvelto _dotsOperationsForSvelto;
bool _isReady;
unsafe JobHandle* _jobHandle;


+ 0
- 35
Extensions/Unity/MonoScheduler.cs View File

@@ -1,35 +0,0 @@
#if UNITY_5 || UNITY_5_3_OR_NEWER
using System.Collections;
using UnityEngine;

namespace Svelto.ECS.Schedulers.Unity
{
class MonoScheduler : MonoBehaviour
{
public MonoScheduler()
{
_coroutine = Coroutine();
}

void Update()
{
_coroutine.MoveNext();
}
IEnumerator Coroutine()
{
while (true)
{
yield return _wait;
onTick();
}
}

readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame();
readonly IEnumerator _coroutine;
internal System.Action onTick;
}
}
#endif

+ 0
- 52
Extensions/Unity/UnityEntitiesSubmissionScheduler.cs View File

@@ -1,52 +0,0 @@
#if UNITY_5 || UNITY_5_3_OR_NEWER
using System;
using Object = UnityEngine.Object;
using UnityEngine;

namespace Svelto.ECS.Schedulers.Unity
{
//The EntitySubmissionScheduler has been introduced to make the entity components submission logic platform independent
//You can customize the scheduler if you wish
public class UnityEntitiesSubmissionScheduler : EntitiesSubmissionScheduler
{
public UnityEntitiesSubmissionScheduler(string name)
{
_scheduler = new GameObject(name).AddComponent<MonoScheduler>();
GameObject.DontDestroyOnLoad(_scheduler.gameObject);
_scheduler.onTick = SubmitEntities;
}

public override void Dispose()
{
if (_scheduler != null && _scheduler.gameObject != null)
{
Object.Destroy(_scheduler.gameObject);
}
}

void SubmitEntities()
{
try
{
_onTick.SubmitEntities();
}
catch (Exception e)
{
paused = true;
Svelto.Console.LogException(e);
throw;
}
}

protected internal override EnginesRoot.EntitiesSubmitter onTick
{
set => _onTick = value;
}

readonly MonoScheduler _scheduler;
EnginesRoot.EntitiesSubmitter _onTick;
}
}
#endif

+ 133
- 40
README.md View File

@@ -10,67 +10,160 @@ Svelto.ECS is easy to start with, but full of tricks for expert users. The harde
simplest setup:

```csharp
using System;
using Svelto.ECS;
using Svelto.ECS.Schedulers;
using Svelto.ECS.Vanilla.Example.SimpleEntityEngine;

/// <summary>
/// This is the common pattern to declare Svelto Exclusive Groups (usually split by composition root)
/// </summary>
public static class ExclusiveGroups
{
public static ExclusiveGroup group0 = new ExclusiveGroup();
public static ExclusiveGroup group1 = new ExclusiveGroup();
}

namespace Svelto.ECS.Vanilla.Example
{
/// <summary>
/// The Context is the application starting point.
/// As a Composition root, it gives to the coder the responsibility to create, initialize and
/// inject dependencies.
/// Every application can have more than one context and every context can have one
/// or more composition roots (a facade, but even a factory, can be a composition root)
/// </summary>
public class SimpleContext
{
//the group where the entity will be built in
public static ExclusiveGroup group0 = new ExclusiveGroup();

public SimpleContext()
{
var simpleSubmissionEntityViewScheduler = new SimpleEntitiesSubmissionScheduler();
//Build Svelto Entities and Engines container, called EnginesRoot
_enginesRoot = new EnginesRoot(simpleSubmissionEntityViewScheduler);

var entityFactory = _enginesRoot.GenerateEntityFactory();

//Add an Engine to the enginesRoot to manage the SimpleEntities
var behaviourForEntityClassEngine = new BehaviourForEntityClassEngine();
//an entity submission scheduler is needed to submit entities to the Svelto database, Svelto is not
//responsible to decide when to submit entities, it's the user's responsibility to do so.
var entitySubmissionScheduler = new SimpleEntitiesSubmissionScheduler();
//An EnginesRoot holds all the engines and entities created. it needs a EntitySubmissionScheduler to know when to
//add previously built entities to the Svelto database. Using the SimpleEntitiesSubmissionScheduler
//is expected as it gives complete control to the user about when the submission happens
_enginesRoot = new EnginesRoot(entitySubmissionScheduler);

//an entity factory allows to build entities inside engines
var entityFactory = _enginesRoot.GenerateEntityFactory();
//the entity functions allows other operations on entities, like remove and swap
var entityFunctions = _enginesRoot.GenerateEntityFunctions();

//Add the Engine to manage the SimpleEntities
var behaviourForEntityClassEngine = new BehaviourForEntityClassEngine(entityFunctions);
_enginesRoot.AddEngine(behaviourForEntityClassEngine);

//build a new Entity with ID 0 in group0
//build Entity with ID 0 in group0
entityFactory.BuildEntity<SimpleEntityDescriptor>(new EGID(0, ExclusiveGroups.group0));

//submit the previously built entities to the Svelto database
simpleSubmissionEntityViewScheduler.SubmitEntities();
entitySubmissionScheduler.SubmitEntities();

//as Svelto doesn't provide an engine ticking system, it's the user's responsibility to
//update engines
//as Svelto doesn't provide an engine/system ticking system, it's the user's responsibility to
//update engines
behaviourForEntityClassEngine.Update();

Console.Log("Done - click any button to quit");

System.Console.ReadKey();

Environment.Exit(0);
}
readonly EnginesRoot _enginesRoot;
}
```

your first entity descriptor:
readonly EnginesRoot _enginesRoot;
}

```csharp
//An EntityComponent must always implement the IEntityComponent interface
//don't worry, boxing/unboxing will never happen.
public struct EntityComponent : IEntityComponent
{
public int counter;
public int counter;
}

class SimpleEntityDescriptor : GenericEntityDescriptor<EntityComponent>
{}
```
your first engine executing entities behaviours:
/// <summary>
/// The EntityDescriptor identifies your Entity. It's essential to identify
/// your entities with a name that comes from the Game Design domain.
/// </summary>
class SimpleEntityDescriptor : GenericEntityDescriptor<EntityComponent> { }

```csharp
public class BehaviourForEntityClassEngine : IQueryingEntitiesEngine
namespace SimpleEntityEngine
{
public EntitiesDB entitiesDB { get; set; }

public void Ready() { }

public void Update()
public class BehaviourForEntityClassEngine :
//this interface makes the engine reactive, it's absolutely optional, you need to read my articles
//and wiki about it.
IReactOnAddEx<EntityComponent>, IReactOnSwapEx<EntityComponent>, IReactOnRemoveEx<EntityComponent>,
//while this interface is optional too, it's almost always used as it gives access to the entitiesDB
IQueryingEntitiesEngine
{
var (components, count) = entitiesDB.QueryEntities<EntityComponent>(ExclusiveGroups.group0);

for (var i = 0; i < count; i++)
components[i].counter++;
//extra entity functions
readonly IEntityFunctions _entityFunctions;

public BehaviourForEntityClassEngine(IEntityFunctions entityFunctions)
{
_entityFunctions = entityFunctions;
}

public EntitiesDB entitiesDB { get; set; }

public void Ready() { }

public void Update()
{
//Simple query to get all the entities with EntityComponent in group1
var (components, entityIDs, count) = entitiesDB.QueryEntities<EntityComponent>(ExclusiveGroups.group1);

uint entityID;
for (var i = 0; i < count; i++)
{
components[i].counter++;
entityID = entityIDs[i];
}

Console.Log("Entity Struct engine executed");
}

//the following methods are called by Svelto.ECS when an entity is added to a group
public void Add((uint start, uint end) rangeOfEntities, in EntityCollection<EntityComponent> entities
, ExclusiveGroupStruct groupID)
{
var (_, entityIDs, _) = entities;

for (uint index = rangeOfEntities.start; index < rangeOfEntities.end; index++)
//Swap entities between groups is a very common operation and it's necessary to
//move entities between groups/sets. A Group represent a state/adjective of an entity, so changing
//group means change state/behaviour as different engines will process different groups.
//it's the Svelto equivalent of adding/remove components to an entity at run time
_entityFunctions.SwapEntityGroup<SimpleEntityDescriptor>(new EGID(entityIDs[index], groupID), ExclusiveGroups.group1);
}

//the following methods are called by Svelto.ECS when an entity is swapped from a group to another
public void MovedTo((uint start, uint end) rangeOfEntities, in EntityCollection<EntityComponent> entities
, ExclusiveGroupStruct fromGroup, ExclusiveGroupStruct toGroup)
{
var (_, entityIDs, _) = entities;
for (var index = rangeOfEntities.start; index < rangeOfEntities.end; index++)
{
Console.Log($"entity {entityIDs[index]} moved from {fromGroup} to {toGroup}");
//like for the swap operation, removing entities from the Svelto database is a very common operation.
//For both operations is necessary to specify the EntityDescriptor to use. This has also a philosophical
//reason to happen, it's to always remind which entity type we are operating with.
_entityFunctions.RemoveEntity<SimpleEntityDescriptor>(new EGID(entityIDs[index], toGroup));
}
}

//the following methods are called by Svelto.ECS when an entity is removed from a group
public void Remove((uint start, uint end) rangeOfEntities, in EntityCollection<EntityComponent> entities, ExclusiveGroupStruct groupID)
{
var (_, entityIDs, _) = entities;

for (uint index = rangeOfEntities.start; index < rangeOfEntities.end; index++)
Console.Log($"entity {entityIDs[index]} removed from {groupID.ToString()}");
}
}
}
}
```

learn more about svelto on the Wiki page: https://github.com/sebas77/Svelto.ECS/wiki
@@ -100,10 +193,10 @@ _Svelto.ECS is lean. It hasn't been designed to move a whole engine such as Unit
Svelto.ECS is partially compatible with Unity 2019.3.x cycle as long as it's not used with any DOTS package (including collections). It is compatible with all the versions of Unity from 2020 and above.

### Svelto.ECS is fully compatible with DOTS Burst and Jobs.
Svelto.ECS is designed to take full advantange of the DOTS modules and to use specifically DOTS ECS as an engine library, through the (optional) SveltoOnDOTS wrapper.
Svelto.ECS is designed to take full advantange of the DOTS modules and to use specifically DOTS ECS as an engine library, through the (optional) SveltoOnDOTS wrapper. Svelto.ECS native components and interfaces are fully compatible with Burst.

## Why using Svelto.ECS without Unity?
There are so many c# game engines out there (Stride, Flax, Monogame, FlatRedBall, Evergine, UnrealCLR, UniEngine just to mention some) and Svelto.ECS is compatible with all of them!
There are so many c# game engines out there (Stride, Flax, Monogame, FlatRedBall, Evergine, Godot, UniEngine just to mention some) and Svelto.ECS is compatible with all of them!

## Performance considerations
Aside from resizing the database absolutely when necessary, all the Svelto operations are memory allocation free. Some containers may need to be preallocated (and then disposed) but those are already advanced scenarios. When using pure ECS (no EntityViewComponents) components are stored in native collections across all the platforms, which means gaining some performance from losing the managed memory checks. With pure ECS, iterating components is automatically cache-friendly.


+ 2
- 2
package.json View File

@@ -11,7 +11,7 @@
"url": "https://github.com/sebas77/Svelto.ECS.git"
},
"dependencies": {
"com.sebaslab.svelto.common": "3.5.0"
"com.sebaslab.svelto.common": "3.5.1"
},
"keywords": [
"svelto",
@@ -19,7 +19,7 @@
"svelto.ecs"
],
"name": "com.sebaslab.svelto.ecs",
"version": "3.5.0-pre",
"version": "3.5.1",
"type": "library",
"unity": "2020.3"
}

Loading…
Cancel
Save