git-subtree-dir: Svelto.ECS git-subtree-mainline:tags/2.9.36b16b0cec2
git-subtree-split:88d8fe6195
@@ -0,0 +1,3 @@ | |||
# These are supported funding model platforms | |||
custom: https://www.paypal.me/smandala |
@@ -0,0 +1,3 @@ | |||
/obj | |||
/bin/Release/netstandard2.0 | |||
/bin/Debug/netstandard2.0 |
@@ -0,0 +1,3 @@ | |||
[submodule "Svelto.Common"] | |||
path = Svelto.Common | |||
url = https://github.com/sebas77/Svelto.Common |
@@ -0,0 +1,21 @@ | |||
MIT License | |||
Copyright (c) 2018 Sebastiano Mandalà | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
in the Software without restriction, including without limitation the rights | |||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |||
copies of the Software, and to permit persons to whom the Software is | |||
furnished to do so, subject to the following conditions: | |||
The above copyright notice and this permission notice shall be included in all | |||
copies or substantial portions of the Software. | |||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |||
SOFTWARE. |
@@ -0,0 +1,84 @@ | |||
[](https://openupm.com/packages/com.sebaslab.svelto.ecs/) | |||
# Svelto Entity Component System 2.9 | |||
===================================== | |||
Real ECS framework for c\#. Enables to write encapsulated, decoupled, maintainable, highly efficient, data oriented, cache friendly, multi-threaded (if used with Svelto.Tasks), code without pain. Although the framework is platform agnostic \(compatible with c\# 7 and .net standard 2.0\), it comes with several Unity extensions. | |||
## Why using Svelto.ECS with Unity? | |||
_Svelto.ECS wasn't born just from the needs of a large team, but also as result of years of reasoning behind software engineering applied to game development\(\*\). Compared to Unity.ECS, the main goals and reasons for Svelto.ECS to exist are different enough to justify its on going development \(plus Svelto is platform agnostic, so it has been written with portability in mind\). Svelto.ECS hasn't been written just to develop faster code, it has been built to help develop better code. Performance gain is one of the benefits in using Svelto.ECS, as ECS in general is a great way to write cache-friendly code. However Svelto.ECS has been designed around the shift of paradigm from Object Oriented Programming and the consequent improvement of the code design and maintainability. Svelto.ECS is the result of years of iteration of the ECS paradigm applied to real game development with the intent to be "junior coder proof"._ | |||
## Official Examples | |||
* **Mini Examples**: [https://github.com/sebas77/Svelto.MiniExamples](https://github.com/sebas77/Svelto.MiniExamples) \(including articles\) | |||
* **Unity Boids Simulation**: [https://github.com/sebas77/Svelto.ECS.Examples.Boids](https://github.com/sebas77/Svelto.ECS.Examples.Boids) \(including article\) | |||
* **Unit Tests**: [https://github.com/sebas77/Svelto.ECS.Tests](https://github.com/sebas77/Svelto.ECS.Tests) | |||
**Official Chat \(join to get help from me!\)** | |||
* [https://discord.gg/3qAdjDb](https://discord.gg/3qAdjDb) | |||
## Official Articles | |||
**Framework articles:** | |||
* [http://www.sebaslab.com/introducing-svelto-ecs-2-9/](http://www.sebaslab.com/introducing-svelto-ecs-2-9/) \(shows what's changed since 2.8\) | |||
* [http://www.sebaslab.com/introducing-svelto-ecs-2-8/](http://www.sebaslab.com/introducing-svelto-ecs-2-8/) \(shows what's changed since 2.7\) | |||
* [http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/](http://www.sebaslab.com/svelto-2-7-whats-new-and-best-practices/) \(shows what's changed since 2.5\) | |||
* [http://www.sebaslab.com/svelto-ecs-2-5-and-allocation-0-code/](http://www.sebaslab.com/svelto-ecs-2-5-and-allocation-0-code/) \(shows what's changed since 2.0\) | |||
* [http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/](http://www.sebaslab.com/svelto-ecs-2-0-almost-production-ready/) \(shows what's changed since 1.0\) | |||
* [http://www.sebaslab.com/ecs-1-0/](http://www.sebaslab.com/ecs-1-0/) | |||
* [http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/](http://www.sebaslab.com/learning-svelto-ecs-by-example-the-unity-survival-example/) | |||
* [http://www.sebaslab.com/learning-svelto-ecs-by-example-the-vanilla-example/](http://www.sebaslab.com/learning-svelto-ecs-by-example-the-vanilla-example/) | |||
* [http://www.sebaslab.com/svelto-ecs-svelto-tasks-to-write-data-oriented-cache-friendly-multi-threaded-code-in-unity/](http://www.sebaslab.com/svelto-ecs-svelto-tasks-to-write-data-oriented-cache-friendly-multi-threaded-code-in-unity/) | |||
**Theory related articles \(in order of publishing date\):** | |||
* [http://www.sebaslab.com/ioc-container-for-unity3d-part-1/](http://www.sebaslab.com/ioc-container-for-unity3d-part-1/) | |||
* [http://www.sebaslab.com/ioc-container-for-unity3d-part-2/](http://www.sebaslab.com/ioc-container-for-unity3d-part-2/) | |||
* [http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-i-dependency-injection/](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-i-dependency-injection/) | |||
* [http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-ii-inversion-of-control/](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-ii-inversion-of-control/) | |||
* [http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iii-entity-component-systems/](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iii-entity-component-systems/) | |||
* [http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iv-dependency-inversion-principle/](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-iv-dependency-inversion-principle/) | |||
* [http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-v-drifting-away-from-ioc-containers/](http://www.sebaslab.com/the-truth-behind-inversion-of-control-part-v-drifting-away-from-ioc-containers/) | |||
* [http://www.sebaslab.com/the-quest-for-maintainable-code-and-the-path-to-ecs/](http://www.sebaslab.com/the-quest-for-maintainable-code-and-the-path-to-ecs/) | |||
Note: I included the IoC articles just to show how I shifted over the years from using an IoC container to use an ECS framework and the rationale behind its adoption. | |||
**The perfect companion for Svelto.ECS is Svelto.Tasks to run the logic of the Systems even on other threads!** | |||
* [https://github.com/sebas77/Svelto.Tasks](https://github.com/sebas77/Svelto.Tasks) | |||
## Users Generated Content \(may use old versions of Svelto and be quite outdated\) | |||
* [https://github.com/grovemaster/Unity3D-Game-App](https://github.com/grovemaster/Unity3D-Game-App) | |||
* [https://github.com/colonelsalt/ZombieDeathBoomECS](https://github.com/colonelsalt/ZombieDeathBoomECS) | |||
* [https://eagergames.wordpress.com/category/ecs/](https://eagergames.wordpress.com/category/ecs/) \(Dario Oliveri\) | |||
* [https://blogs.msdn.microsoft.com/uk\_faculty\_connection/2018/05/08/entity-component-system-in-unity-a-tutorial/](https://blogs.msdn.microsoft.com/uk_faculty_connection/2018/05/08/entity-component-system-in-unity-a-tutorial/) \(Lee Stott\) | |||
* [https://github.com/sebas77/Svelto.ECS.Debugger](https://github.com/sebas77/Svelto.ECS.Debugger) \(work just started\) | |||
* [https://github.com/NED-Studio/LGK.Inspector](https://github.com/NED-Studio/LGK.Inspector) \(probably not working anymore\) | |||
## In case of bugs | |||
Best option is to fork and clone [https://github.com/sebas77/Svelto.ECS.Tests](https://github.com/sebas77/Svelto.ECS.Tests), add a new test to reproduce the problem and request a pull. Then open a github, I come here pretty often :\). Also feel free to contact me on twitter or leave comments on the blog! | |||
## [The Github wiki page](https://github.com/sebas77/Svelto.ECS/wiki) | |||
It needs love and as far as I understood, anyone can edit it. Feel free to do so if you have a good understanding of Svelto! | |||
## I like the project, how can I help? | |||
Hey thanks a lot for considering this. You can help in several ways. The simplest is to talk about Svelto.ECS and spread the word, more we are, better it is for the community. Then you can help with the documentation, updating the wiki or writing your own articles. Svelto.ECS has all the features needed to make a game with the ECS pattern, but some areas are lacking: A visual debugger and more unit tests are needed. Other platforms other than Unity could get some love too: Xenko, Godot and monogame. Porting to other languages, expecially c++, would be awesome! | |||
## Svelto Framework is used to develop the following products\(\*\): | |||
 | |||
 | |||
 | |||
\*if you want your products made with Svelto here, just send me an email or whatever, I'll be super happy to add them. | |||
_**Note: Dear Svelto Users : Although I am committed to help you and write articles as much as I can, I will never be able to keep all the documentation up to date. If you are a happy svelto user and you want to contribute, please feel free to update the github wiki! 🙏👊**_ | |||
@@ -0,0 +1,4 @@ | |||
# Table of contents | |||
* [Svelto Entity Component System 2.8](README.md) | |||
@@ -0,0 +1 @@ | |||
Subproject commit bde1316f7c72c790de157ecc19d124187cbe86fc |
@@ -0,0 +1,8 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public class AllowMultipleAttribute : Attribute | |||
{ | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 29ca2992915771b4eb047f00a7a4b7a6 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,81 @@ | |||
#if DEBUG && !PROFILER | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
#else | |||
using System.Diagnostics; | |||
#endif | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
#if DEBUG && !PROFILER | |||
void CheckRemoveEntityID(EGID egid) | |||
{ | |||
// Console.LogError("<color=orange>removed</color>".FastConcat(egid.ToString())); | |||
if (_idCheckers.TryGetValue(egid.groupID, out var hash)) | |||
{ | |||
if (hash.Contains(egid.entityID) == false) | |||
throw new ECSException("Entity with not found ID is about to be removed: id: " | |||
.FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID)); | |||
hash.Remove(egid.entityID); | |||
if (hash.Count == 0) | |||
_idCheckers.Remove(egid.groupID); | |||
} | |||
else | |||
{ | |||
throw new ECSException("Entity with not found ID is about to be removed: id: " | |||
.FastConcat(egid.entityID) | |||
.FastConcat(" groupid: ") | |||
.FastConcat(egid.groupID)); | |||
} | |||
} | |||
void CheckAddEntityID(EGID egid) | |||
{ | |||
// Console.LogError("<color=orange>added</color> ".FastConcat(egid.ToString())); | |||
if (_idCheckers.TryGetValue(egid.groupID, out var hash) == false) | |||
hash = _idCheckers[egid.groupID] = new HashSet<uint>(); | |||
else | |||
{ | |||
if (hash.Contains(egid.entityID)) | |||
throw new ECSException("Entity with used ID is about to be built: '" | |||
.FastConcat("' id: '") | |||
.FastConcat(egid.entityID) | |||
.FastConcat("' groupid: '") | |||
.FastConcat(egid.groupID) | |||
.FastConcat("'")); | |||
} | |||
hash.Add(egid.entityID); | |||
} | |||
void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) | |||
{ | |||
_idCheckers.Remove(groupID); | |||
} | |||
readonly FasterDictionary<uint, HashSet<uint>> _idCheckers = new FasterDictionary<uint, HashSet<uint>>(); | |||
#else | |||
[Conditional("_CHECKS_DISABLED")] | |||
void CheckRemoveEntityID(EGID egid) | |||
{ | |||
} | |||
[Conditional("_CHECKS_DISABLED")] | |||
void CheckAddEntityID(EGID egid) | |||
{ | |||
} | |||
[Conditional("_CHECKS_DISABLED")] | |||
void RemoveGroupID(ExclusiveGroup.ExclusiveGroupStruct groupID) | |||
{ | |||
} | |||
#endif | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: cda6cb7ffa71b954dbe3711e152399c2 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,446 @@ | |||
#if DISABLE_DBC || !DEBUG || PROFILER | |||
#define DISABLE_CHECKS | |||
using System.Diagnostics; | |||
#endif | |||
using System; | |||
namespace DBC.ECS | |||
{ | |||
/// <summary> | |||
/// Design By Contract Checks. | |||
/// | |||
/// Each method generates an exception or | |||
/// a trace assertion statement if the contract is broken. | |||
/// </summary> | |||
/// <remarks> | |||
/// This example shows how to call the Require method. | |||
/// Assume DBC_CHECK_PRECONDITION is defined. | |||
/// <code> | |||
/// public void Test(int x) | |||
/// { | |||
/// try | |||
/// { | |||
/// Check.Require(x > 1, "x must be > 1"); | |||
/// } | |||
/// catch (System.Exception ex) | |||
/// { | |||
/// Console.WriteLine(ex.ToString()); | |||
/// } | |||
/// } | |||
/// </code> | |||
/// If you wish to use trace assertion statements, intended for Debug scenarios, | |||
/// rather than exception handling then set | |||
/// | |||
/// <code>Check.UseAssertions = true</code> | |||
/// | |||
/// You can specify this in your application entry point and maybe make it | |||
/// dependent on conditional compilation flags or configuration file settings, e.g., | |||
/// <code> | |||
/// #if DBC_USE_ASSERTIONS | |||
/// Check.UseAssertions = true; | |||
/// #endif | |||
/// </code> | |||
/// You can direct output to a Trace listener. For example, you could insert | |||
/// <code> | |||
/// Trace.Listeners.Clear(); | |||
/// Trace.Listeners.Add(new TextWriterTraceListener(Console.Out)); | |||
/// </code> | |||
/// | |||
/// or direct output to a file or the Event Log. | |||
/// | |||
/// (Note: For ASP.NET clients use the Listeners collection | |||
/// of the Debug, not the Trace, object and, for a Release build, only exception-handling | |||
/// is possible.) | |||
/// </remarks> | |||
/// | |||
static class Check | |||
{ | |||
#region Interface | |||
/// <summary> | |||
/// Precondition check. | |||
/// </summary> | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Require(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PreconditionException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Precondition: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Precondition check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Require(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PreconditionException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Precondition: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Precondition check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Require(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PreconditionException("Precondition failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Precondition failed."); | |||
} | |||
} | |||
/// <summary> | |||
/// Postcondition check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Ensure(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PostconditionException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Postcondition: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Postcondition check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Ensure(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PostconditionException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Postcondition: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Postcondition check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Ensure(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new PostconditionException("Postcondition failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Postcondition failed."); | |||
} | |||
} | |||
/// <summary> | |||
/// Invariant check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Invariant(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new InvariantException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Invariant: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Invariant check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Invariant(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new InvariantException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Invariant: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Invariant check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Invariant(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new InvariantException("Invariant failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Invariant failed."); | |||
} | |||
} | |||
/// <summary> | |||
/// Assertion check. | |||
/// </summary> | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Assert(bool assertion, string message) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new AssertionException(message); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Assertion: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Assertion check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Assert(bool assertion, string message, Exception inner) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new AssertionException(message, inner); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Assertion: " + message); | |||
} | |||
} | |||
/// <summary> | |||
/// Assertion check. | |||
/// </summary> | |||
/// | |||
#if DISABLE_CHECKS | |||
[Conditional("__NEVER_DEFINED__")] | |||
#endif | |||
public static void Assert(bool assertion) | |||
{ | |||
if (UseExceptions) | |||
{ | |||
if (!assertion) | |||
throw new AssertionException("Assertion failed."); | |||
} | |||
else | |||
{ | |||
Trace.Assert(assertion, "Assertion failed."); | |||
} | |||
} | |||
/// <summary> | |||
/// Set this if you wish to use Trace Assert statements | |||
/// instead of exception handling. | |||
/// (The Check class uses exception handling by default.) | |||
/// </summary> | |||
public static bool UseAssertions | |||
{ | |||
get | |||
{ | |||
return useAssertions; | |||
} | |||
set | |||
{ | |||
useAssertions = value; | |||
} | |||
} | |||
#endregion // Interface | |||
#region Implementation | |||
// No creation | |||
/// <summary> | |||
/// Is exception handling being used? | |||
/// </summary> | |||
static bool UseExceptions | |||
{ | |||
get | |||
{ | |||
return !useAssertions; | |||
} | |||
} | |||
// Are trace assertion statements being used? | |||
// Default is to use exception handling. | |||
static bool useAssertions; | |||
#endregion // Implementation | |||
} // End Check | |||
internal class Trace | |||
{ | |||
internal static void Assert(bool assertion, string v) | |||
{ | |||
#if NETFX_CORE | |||
System.Diagnostics.Contracts.Contract.Assert(assertion, v); | |||
#else | |||
System.Diagnostics.Trace.Assert(assertion, v); | |||
#endif | |||
} | |||
} | |||
#region Exceptions | |||
/// <summary> | |||
/// Exception raised when a contract is broken. | |||
/// Catch this exception type if you wish to differentiate between | |||
/// any DesignByContract exception and other runtime exceptions. | |||
/// | |||
/// </summary> | |||
public class DesignByContractException : Exception | |||
{ | |||
protected DesignByContractException() {} | |||
protected DesignByContractException(string message) : base(message) {} | |||
protected DesignByContractException(string message, Exception inner) : base(message, inner) {} | |||
} | |||
/// <summary> | |||
/// Exception raised when a precondition fails. | |||
/// </summary> | |||
public class PreconditionException : DesignByContractException | |||
{ | |||
/// <summary> | |||
/// Precondition Exception. | |||
/// </summary> | |||
public PreconditionException() {} | |||
/// <summary> | |||
/// Precondition Exception. | |||
/// </summary> | |||
public PreconditionException(string message) : base(message) {} | |||
/// <summary> | |||
/// Precondition Exception. | |||
/// </summary> | |||
public PreconditionException(string message, Exception inner) : base(message, inner) {} | |||
} | |||
/// <summary> | |||
/// Exception raised when a postcondition fails. | |||
/// </summary> | |||
public class PostconditionException : DesignByContractException | |||
{ | |||
/// <summary> | |||
/// Postcondition Exception. | |||
/// </summary> | |||
public PostconditionException() {} | |||
/// <summary> | |||
/// Postcondition Exception. | |||
/// </summary> | |||
public PostconditionException(string message) : base(message) {} | |||
/// <summary> | |||
/// Postcondition Exception. | |||
/// </summary> | |||
public PostconditionException(string message, Exception inner) : base(message, inner) {} | |||
} | |||
/// <summary> | |||
/// Exception raised when an invariant fails. | |||
/// </summary> | |||
public class InvariantException : DesignByContractException | |||
{ | |||
/// <summary> | |||
/// Invariant Exception. | |||
/// </summary> | |||
public InvariantException() {} | |||
/// <summary> | |||
/// Invariant Exception. | |||
/// </summary> | |||
public InvariantException(string message) : base(message) {} | |||
/// <summary> | |||
/// Invariant Exception. | |||
/// </summary> | |||
public InvariantException(string message, Exception inner) : base(message, inner) {} | |||
} | |||
/// <summary> | |||
/// Exception raised when an assertion fails. | |||
/// </summary> | |||
public class AssertionException : DesignByContractException | |||
{ | |||
/// <summary> | |||
/// Assertion Exception. | |||
/// </summary> | |||
public AssertionException() {} | |||
/// <summary> | |||
/// Assertion Exception. | |||
/// </summary> | |||
public AssertionException(string message) : base(message) {} | |||
/// <summary> | |||
/// Assertion Exception. | |||
/// </summary> | |||
public AssertionException(string message, Exception inner) : base(message, inner) {} | |||
} | |||
#endregion // Exception classes | |||
} // End Design By Contract |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: f25d12935fd762e40841aa5c56e603e4 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 9d98d8ed291a59c4890021f6391142ca | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,46 @@ | |||
using System; | |||
using System.Linq.Expressions; | |||
using System.Reflection; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
static class SetEGIDWithoutBoxing<T> where T : struct, IEntityStruct | |||
{ | |||
internal delegate void ActionCast(ref T target, EGID egid); | |||
public static readonly ActionCast SetIDWithoutBoxing = MakeSetter(); | |||
static ActionCast MakeSetter() | |||
{ | |||
if (EntityBuilder<T>.HAS_EGID) | |||
{ | |||
#if !ENABLE_IL2CPP | |||
Type myTypeA = typeof(T); | |||
PropertyInfo myFieldInfo = myTypeA.GetProperty("ID"); | |||
ParameterExpression targetExp = Expression.Parameter(typeof(T).MakeByRefType(), "target"); | |||
ParameterExpression valueExp = Expression.Parameter(typeof(EGID), "value"); | |||
MemberExpression fieldExp = Expression.Property(targetExp, myFieldInfo); | |||
BinaryExpression assignExp = Expression.Assign(fieldExp, valueExp); | |||
var setter = Expression.Lambda<ActionCast>(assignExp, targetExp, valueExp).Compile(); | |||
return setter; | |||
#else | |||
return (ref T target, EGID value) => | |||
{ | |||
var needEgid = (target as INeedEGID); | |||
needEgid.ID = value; | |||
target = (T) needEgid; | |||
}; | |||
#endif | |||
} | |||
return null; | |||
} | |||
public static void Warmup() | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 1e8610cc771cdeb46a808788e49b21d1 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,222 @@ | |||
using System; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
public interface ITypeSafeDictionary | |||
{ | |||
int Count { get; } | |||
ITypeSafeDictionary Create(); | |||
void AddEntitiesToEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDb, | |||
ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); | |||
void RemoveEntitiesFromEngines(FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group); | |||
void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId); | |||
void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines, in PlatformProfiler profiler); | |||
void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup); | |||
void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler); | |||
void SetCapacity(uint size); | |||
void Trim(); | |||
void Clear(); | |||
void FastClear(); | |||
bool Has(uint entityIdEntityId); | |||
} | |||
class TypeSafeDictionary<TValue> : FasterDictionary<uint, TValue>, | |||
ITypeSafeDictionary where TValue : struct, IEntityStruct | |||
{ | |||
static readonly Type _type = typeof(TValue); | |||
static readonly string _typeName = _type.Name; | |||
static readonly bool _hasEgid = typeof(INeedEGID).IsAssignableFrom(_type); | |||
public TypeSafeDictionary(uint size) : base(size) {} | |||
public TypeSafeDictionary() {} | |||
public void AddEntitiesFromDictionary(ITypeSafeDictionary entitiesToSubmit, uint groupId) | |||
{ | |||
var typeSafeDictionary = entitiesToSubmit as TypeSafeDictionary<TValue>; | |||
foreach (var tuple in typeSafeDictionary) | |||
{ | |||
try | |||
{ | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref tuple.Value, new EGID(tuple.Key, groupId)); | |||
Add(tuple.Key, tuple.Value); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new TypeSafeDictionaryException( | |||
"trying to add an EntityView with the same ID more than once Entity: " | |||
.FastConcat(typeof(TValue).ToString()).FastConcat(", group ").FastConcat(groupId).FastConcat(", id ").FastConcat(tuple.Key), e); | |||
} | |||
} | |||
} | |||
public void AddEntitiesToEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
ITypeSafeDictionary realDic, in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) | |||
{ | |||
var typeSafeDictionary = realDic as TypeSafeDictionary<TValue>; | |||
//this can be optimized, should pass all the entities and not restart the process for each one | |||
foreach (var value in this) | |||
AddEntityViewToEngines(entityViewEnginesDB, ref typeSafeDictionary.GetValueByRef(value.Key), null, | |||
in profiler, new EGID(value.Key, group)); | |||
} | |||
public void RemoveEntitiesFromEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
in PlatformProfiler profiler, ExclusiveGroup.ExclusiveGroupStruct @group) | |||
{ | |||
foreach (var value in this) | |||
RemoveEntityViewFromEngines(entityViewEnginesDB, ref GetValueByRef(value.Key), null, in profiler, | |||
new EGID(value.Key, group)); | |||
} | |||
public bool Has(uint entityIdEntityId) | |||
{ | |||
return ContainsKey(entityIdEntityId); | |||
} | |||
public void RemoveEntityFromDictionary(EGID fromEntityGid, in PlatformProfiler profiler) | |||
{ | |||
Remove(fromEntityGid.entityID); | |||
} | |||
public void AddEntityToDictionary(EGID fromEntityGid, EGID toEntityID, ITypeSafeDictionary toGroup) | |||
{ | |||
var valueIndex = GetIndex(fromEntityGid.entityID); | |||
if (toGroup != null) | |||
{ | |||
var toGroupCasted = toGroup as TypeSafeDictionary<TValue>; | |||
ref var entity = ref valuesArray[valueIndex]; | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID); | |||
toGroupCasted.Add(fromEntityGid.entityID, entity); | |||
} | |||
} | |||
public void MoveEntityFromEngines(EGID fromEntityGid, EGID? toEntityID, ITypeSafeDictionary toGroup, | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines, in PlatformProfiler profiler) | |||
{ | |||
var valueIndex = GetIndex(fromEntityGid.entityID); | |||
ref var entity = ref valuesArray[valueIndex]; | |||
if (toGroup != null) | |||
{ | |||
RemoveEntityViewFromEngines(engines, ref entity, fromEntityGid.groupID, in profiler, | |||
fromEntityGid); | |||
var toGroupCasted = toGroup as TypeSafeDictionary<TValue>; | |||
var previousGroup = fromEntityGid.groupID; | |||
if (_hasEgid) SetEGIDWithoutBoxing<TValue>.SetIDWithoutBoxing(ref entity, toEntityID.Value); | |||
var index = toGroupCasted.GetIndex(toEntityID.Value.entityID); | |||
AddEntityViewToEngines(engines, ref toGroupCasted.valuesArray[index], previousGroup, | |||
in profiler, toEntityID.Value); | |||
} | |||
else | |||
RemoveEntityViewFromEngines(engines, ref entity, null, in profiler, fromEntityGid); | |||
} | |||
public ITypeSafeDictionary Create() | |||
{ | |||
return new TypeSafeDictionary<TValue>(); | |||
} | |||
void AddEntityViewToEngines(FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> entityViewEnginesDB, | |||
ref TValue entity, ExclusiveGroup.ExclusiveGroupStruct? previousGroup, | |||
in PlatformProfiler profiler, EGID egid) | |||
{ | |||
//get all the engines linked to TValue | |||
if (!entityViewEnginesDB.TryGetValue(new RefWrapper<Type>(_type), out var entityViewsEngines)) return; | |||
if (previousGroup == null) | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
{ | |||
(entityViewsEngines[i] as IReactOnAddAndRemove<TValue>).Add(ref entity, egid); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Add callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
} | |||
else | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
{ | |||
(entityViewsEngines[i] as IReactOnSwap<TValue>).MovedTo(ref entity, previousGroup.Value, | |||
egid); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside MovedTo callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
} | |||
} | |||
static void RemoveEntityViewFromEngines( | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> @group, ref TValue entity, | |||
ExclusiveGroup.ExclusiveGroupStruct? previousGroup, in PlatformProfiler profiler, EGID egid) | |||
{ | |||
if (!@group.TryGetValue(new RefWrapper<Type>(_type), out var entityViewsEngines)) return; | |||
if (previousGroup == null) | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
(entityViewsEngines[i] as IReactOnAddAndRemove<TValue>).Remove(ref entity, egid); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
} | |||
#if SEEMS_UNNECESSARY | |||
else | |||
{ | |||
for (var i = 0; i < entityViewsEngines.Count; i++) | |||
try | |||
{ | |||
using (profiler.Sample(entityViewsEngines[i], _typeName)) | |||
(entityViewsEngines[i] as IReactOnSwap<TValue>).MovedFrom(ref entity, egid); | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException( | |||
"Code crashed inside Remove callback ".FastConcat(typeof(TValue).ToString()), e); | |||
} | |||
} | |||
#endif | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 5e812f7d5c7a03d4faebd6bbc9a06a2a | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,12 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public class TypeSafeDictionaryException : Exception | |||
{ | |||
public TypeSafeDictionaryException(string message, Exception exception) : | |||
base(message, exception) | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 8c21456b4b0c7c9409264d02670f7f4c | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: 67be04a8cde247c4d93885b1ff03ddec | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,21 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public class DispatchOnChange<T> : DispatchOnSet<T> where T:IEquatable<T> | |||
{ | |||
public DispatchOnChange(EGID senderID) : base(senderID) | |||
{ } | |||
public new T value | |||
{ | |||
set | |||
{ | |||
if (value.Equals(_value) == false) | |||
base.value = value; | |||
} | |||
get => _value; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 8b6d96ff87b04c140ad95db4cb267540 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,42 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public class DispatchOnSet<T> | |||
{ | |||
public DispatchOnSet(EGID senderID) | |||
{ | |||
_senderID = senderID; | |||
} | |||
public T value | |||
{ | |||
set | |||
{ | |||
_value = value; | |||
if (_paused == false) | |||
_subscribers(_senderID, value); | |||
} | |||
} | |||
public void NotifyOnValueSet(Action<EGID, T> action) | |||
{ | |||
_subscribers += action; | |||
} | |||
public void StopNotify(Action<EGID, T> action) | |||
{ | |||
_subscribers -= action; | |||
} | |||
public void PauseNotify() { _paused = true; } | |||
public void ResumeNotify() { _paused = false; } | |||
protected T _value; | |||
readonly EGID _senderID; | |||
Action<EGID, T> _subscribers; | |||
bool _paused; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: f3455e49716d5f44c9ed8da0880dead2 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,128 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// DynamicEntityDescriptor can be used to add entity views to an existing EntityDescriptor that act as flags, | |||
/// at building time. | |||
/// This method allocates, so it shouldn't be abused | |||
/// </summary> | |||
/// <typeparam name="TType"></typeparam> | |||
public struct DynamicEntityDescriptor<TType> : IEntityDescriptor where TType : IEntityDescriptor, new() | |||
{ | |||
internal DynamicEntityDescriptor(bool isExtendible) : this() | |||
{ | |||
var defaultEntities = EntityDescriptorTemplate<TType>.descriptor.entitiesToBuild; | |||
var length = defaultEntities.Length; | |||
_entitiesToBuild = new IEntityBuilder[length + 1]; | |||
Array.Copy(defaultEntities, 0, _entitiesToBuild, 0, length); | |||
//assign it after otherwise the previous copy will overwrite the value in case the item | |||
//is already present | |||
_entitiesToBuild[length] = new EntityBuilder<EntityStructInfoView> | |||
( | |||
new EntityStructInfoView | |||
{ | |||
entitiesToBuild = _entitiesToBuild | |||
} | |||
); | |||
} | |||
public DynamicEntityDescriptor(IEntityBuilder[] extraEntityBuilders) : this() | |||
{ | |||
var extraEntitiesLength = extraEntityBuilders.Length; | |||
_entitiesToBuild = Construct(extraEntitiesLength, extraEntityBuilders, | |||
EntityDescriptorTemplate<TType>.descriptor.entitiesToBuild); | |||
} | |||
public DynamicEntityDescriptor(FasterList<IEntityBuilder> extraEntityBuilders) : this() | |||
{ | |||
var extraEntities = extraEntityBuilders.ToArrayFast(); | |||
var extraEntitiesLength = extraEntityBuilders.Count; | |||
_entitiesToBuild = Construct(extraEntitiesLength, extraEntities, | |||
EntityDescriptorTemplate<TType>.descriptor.entitiesToBuild); | |||
} | |||
public void ExtendWith<T>() where T : IEntityDescriptor, new() | |||
{ | |||
var newEntitiesToBuild = EntityDescriptorTemplate<T>.descriptor.entitiesToBuild; | |||
_entitiesToBuild = Construct(newEntitiesToBuild.Length, newEntitiesToBuild, _entitiesToBuild); | |||
} | |||
public void ExtendWith(IEntityBuilder[] extraEntities) | |||
{ | |||
_entitiesToBuild = Construct(extraEntities.Length, extraEntities, _entitiesToBuild); | |||
} | |||
static IEntityBuilder[] Construct(int extraEntitiesLength, IEntityBuilder[] extraEntities, | |||
IEntityBuilder[] startingEntities) | |||
{ | |||
IEntityBuilder[] localEntitiesToBuild; | |||
if (extraEntitiesLength == 0) | |||
{ | |||
localEntitiesToBuild = startingEntities; | |||
return localEntitiesToBuild; | |||
} | |||
var defaultEntities = startingEntities; | |||
var length = defaultEntities.Length; | |||
var index = SetupSpecialEntityStruct(defaultEntities, out localEntitiesToBuild, extraEntitiesLength); | |||
Array.Copy(extraEntities, 0, localEntitiesToBuild, length, extraEntitiesLength); | |||
//assign it after otherwise the previous copy will overwrite the value in case the item | |||
//is already present | |||
localEntitiesToBuild[index] = new EntityBuilder<EntityStructInfoView> | |||
( | |||
new EntityStructInfoView | |||
{ | |||
entitiesToBuild = localEntitiesToBuild | |||
} | |||
); | |||
return localEntitiesToBuild; | |||
} | |||
static int SetupSpecialEntityStruct(IEntityBuilder[] defaultEntities, out IEntityBuilder[] entitiesToBuild, | |||
int extraLenght) | |||
{ | |||
int length = defaultEntities.Length; | |||
int index = -1; | |||
for (var i = 0; i < length; i++) | |||
{ | |||
//the special entity already exists | |||
if (defaultEntities[i].GetEntityType() == EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW) | |||
{ | |||
index = i; | |||
break; | |||
} | |||
} | |||
if (index == -1) | |||
{ | |||
index = length + extraLenght; | |||
entitiesToBuild = new IEntityBuilder[index + 1]; | |||
} | |||
else | |||
entitiesToBuild = new IEntityBuilder[length + extraLenght]; | |||
Array.Copy(defaultEntities, 0, entitiesToBuild, 0, length); | |||
return index; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _entitiesToBuild; | |||
IEntityBuilder[] _entitiesToBuild; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: f44f311aa89faee408fe3591032a1bb9 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,13 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public class ECSException : Exception | |||
{ | |||
public ECSException(string message):base("<color=red>".FastConcat(message, "</color>")) | |||
{} | |||
public ECSException(string message, Exception innerE):base("<color=red>".FastConcat(message, "</color>"), innerE) | |||
{} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: ffc933f6fa0c64f46aa6e501ccac1eca | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: ed2ee43790d8ab0408efcd5ee7b4e80f | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,47 @@ | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Experimental | |||
{ | |||
public struct ECSResources<T> | |||
{ | |||
internal uint id; | |||
public static implicit operator T(ECSResources<T> ecsString) { return ResourcesECSDB<T>.FromECS(ecsString.id); } | |||
} | |||
static class ResourcesECSDB<T> | |||
{ | |||
static readonly FasterList<T> _resources = new FasterList<T>(); | |||
internal static ref T resources(uint id) | |||
{ | |||
return ref _resources[(int) id - 1]; | |||
} | |||
internal static uint ToECS(T resource) | |||
{ | |||
_resources.Add(resource); | |||
return (uint)_resources.Count; | |||
} | |||
public static T FromECS(uint id) | |||
{ | |||
if (id - 1 < _resources.Count) | |||
return _resources[(int) id - 1]; | |||
return default; | |||
} | |||
} | |||
public static class ResourceExtensions | |||
{ | |||
public static void Set<T>(ref this ECSResources<T> resource, T newText) | |||
{ | |||
if (resource.id != 0) | |||
ResourcesECSDB<T>.resources(resource.id) = newText; | |||
else | |||
resource.id = ResourcesECSDB<T>.ToECS(newText); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 5afcd85ad92ffb344b04a1aa675814bf | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,38 @@ | |||
using System; | |||
namespace Svelto.ECS.Experimental | |||
{ | |||
[Serialization.DoNotSerialize] | |||
public struct ECSString:IEquatable<ECSString> | |||
{ | |||
uint id; | |||
public ECSString(string newText) | |||
{ | |||
id = ResourcesECSDB<string>.ToECS(newText); | |||
} | |||
public static implicit operator string(ECSString ecsString) | |||
{ | |||
return ResourcesECSDB<string>.FromECS(ecsString.id); | |||
} | |||
public void Set(string newText) | |||
{ | |||
if (id != 0) | |||
ResourcesECSDB<string>.resources(id) = newText; | |||
else | |||
id = ResourcesECSDB<string>.ToECS(newText); | |||
} | |||
public bool Equals(ECSString other) | |||
{ | |||
return other.id == id; | |||
} | |||
public override string ToString() | |||
{ | |||
return ResourcesECSDB<string>.FromECS(id); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: a851028d58ed0754fa529a196e952859 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,77 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
#pragma warning disable 660,661 | |||
namespace Svelto.ECS | |||
{ | |||
//todo: add debug map | |||
[Serialization.DoNotSerialize] | |||
[Serializable] | |||
public struct EGID:IEquatable<EGID>,IEqualityComparer<EGID>,IComparable<EGID> | |||
{ | |||
public uint entityID => (uint) (_GID & 0xFFFFFFFF); | |||
public ExclusiveGroup.ExclusiveGroupStruct groupID => new ExclusiveGroup.ExclusiveGroupStruct((uint) (_GID >> 32)); | |||
public static bool operator ==(EGID obj1, EGID obj2) | |||
{ | |||
return obj1._GID == obj2._GID; | |||
} | |||
public static bool operator !=(EGID obj1, EGID obj2) | |||
{ | |||
return obj1._GID != obj2._GID; | |||
} | |||
public EGID(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupID) : this() | |||
{ | |||
_GID = MAKE_GLOBAL_ID(entityID, groupID); | |||
} | |||
static ulong MAKE_GLOBAL_ID(uint entityId, uint groupId) | |||
{ | |||
return (ulong)groupId << 32 | ((ulong)entityId & 0xFFFFFFFF); | |||
} | |||
public static explicit operator uint(EGID id) | |||
{ | |||
return id.entityID; | |||
} | |||
//in the way it's used, ulong must be always the same for each id/group | |||
public static explicit operator ulong(EGID id) { return id._GID; } | |||
public bool Equals(EGID other) | |||
{ | |||
return _GID == other._GID; | |||
} | |||
public bool Equals(EGID x, EGID y) | |||
{ | |||
return x == y; | |||
} | |||
public int GetHashCode(EGID obj) | |||
{ | |||
return _GID.GetHashCode(); | |||
} | |||
public int CompareTo(EGID other) | |||
{ | |||
return _GID.CompareTo(other._GID); | |||
} | |||
internal EGID(uint entityID, uint groupID) : this() | |||
{ | |||
_GID = MAKE_GLOBAL_ID(entityID, groupID); | |||
} | |||
public override string ToString() | |||
{ | |||
return "id ".FastConcat(entityID).FastConcat(" group ").FastConcat(groupID); | |||
} | |||
readonly ulong _GID; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 107bb03a12307bb4f961b85b782e22e1 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,37 @@ | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using System.Runtime.InteropServices; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
public struct EGIDMapper<T> where T : struct, IEntityStruct | |||
{ | |||
internal FasterDictionary<uint, T> map; | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T Entity(uint entityID) | |||
{ | |||
#if DEBUG && !PROFILER | |||
if (map.TryFindIndex(entityID, out var findIndex) == false) | |||
throw new Exception("Entity not found in this group ".FastConcat(typeof(T).ToString())); | |||
#else | |||
map.TryFindIndex(entityID, out var findIndex); | |||
#endif | |||
return ref map.valuesArray[findIndex]; | |||
} | |||
public bool TryGetEntity(uint entityID, out T value) | |||
{ | |||
if (map.TryFindIndex(entityID, out var index)) | |||
{ | |||
value = map.GetDirectValue(index); | |||
return true; | |||
} | |||
value = default; | |||
return false; | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: c28b51f353894904c831577c2c725122 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,86 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
internal class DoubleBufferedEntitiesToAdd | |||
{ | |||
const int MaximumNumberOfItemsPerFrameBeforeToClear = 100; | |||
internal void Swap() | |||
{ | |||
Swap(ref current, ref other); | |||
Swap(ref currentEntitiesCreatedPerGroup, ref otherEntitiesCreatedPerGroup); | |||
} | |||
void Swap<T>(ref T item1, ref T item2) | |||
{ | |||
var toSwap = item2; | |||
item2 = item1; | |||
item1 = toSwap; | |||
} | |||
public void ClearOther() | |||
{ | |||
//do not clear the groups created so far, they will be reused, unless they are too many! | |||
var otherCount = other.Count; | |||
if (otherCount > MaximumNumberOfItemsPerFrameBeforeToClear) | |||
{ | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
other.FastClear(); | |||
return; | |||
} | |||
var otherValuesArray = other.valuesArray; | |||
for (int i = 0; i < otherCount; ++i) | |||
{ | |||
var safeDictionariesCount = otherValuesArray[i].Count; | |||
var safeDictionaries = otherValuesArray[i].valuesArray; | |||
//do not remove the dictionaries of entities per type created so far, they will be reused | |||
if (safeDictionariesCount <= MaximumNumberOfItemsPerFrameBeforeToClear) | |||
{ | |||
for (int j = 0; j < safeDictionariesCount; ++j) | |||
{ | |||
//clear the dictionary of entities create do far (it won't allocate though) | |||
safeDictionaries[j].FastClear(); | |||
} | |||
} | |||
else | |||
{ | |||
otherValuesArray[i].FastClear(); | |||
} | |||
} | |||
otherEntitiesCreatedPerGroup.FastClear(); | |||
} | |||
internal FasterDictionary<uint, uint> currentEntitiesCreatedPerGroup; | |||
internal FasterDictionary<uint, uint> otherEntitiesCreatedPerGroup; | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> current; | |||
internal FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> other; | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> | |||
_entityViewsToAddBufferA = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> | |||
_entityViewsToAddBufferB = | |||
new FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>>(); | |||
readonly FasterDictionary<uint, uint> _entitiesCreatedPerGroupA = new FasterDictionary<uint, uint>(); | |||
readonly FasterDictionary<uint, uint> _entitiesCreatedPerGroupB = new FasterDictionary<uint, uint>(); | |||
public DoubleBufferedEntitiesToAdd() | |||
{ | |||
currentEntitiesCreatedPerGroup = _entitiesCreatedPerGroupA; | |||
otherEntitiesCreatedPerGroup = _entitiesCreatedPerGroupB; | |||
current = _entityViewsToAddBufferA; | |||
other = _entityViewsToAddBufferB; | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 317edb22c6fdcb447a576d39d928aae7 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,143 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.ECS.Schedulers; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
public struct EntitiesSubmitter | |||
{ | |||
public EntitiesSubmitter(EnginesRoot enginesRoot) | |||
{ | |||
_weakReference = new DataStructures.WeakReference<EnginesRoot>(enginesRoot); | |||
} | |||
public void Invoke() | |||
{ | |||
if (_weakReference.IsValid) | |||
_weakReference.Target.SubmitEntityViews(); | |||
} | |||
readonly DataStructures.WeakReference<EnginesRoot> _weakReference; | |||
} | |||
/// <summary> | |||
/// Engines root contextualize your engines and entities. You don't need to limit yourself to one EngineRoot | |||
/// as multiple engines root could promote separation of scopes. The EntitySubmissionScheduler checks | |||
/// periodically if new entity must be submitted to the database and the engines. It's an external | |||
/// dependencies to be independent by the running platform as the user can define it. | |||
/// The EntitySubmissionScheduler cannot hold an EnginesRoot reference, that's why | |||
/// it must receive a weak reference of the EnginesRoot callback. | |||
/// </summary> | |||
public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler) | |||
{ | |||
_entitiesOperations = new FasterDictionary<ulong, EntitySubmitOperation>(); | |||
_reactiveEnginesAddRemove = new FasterDictionary<RefWrapper<Type>, FasterList<IEngine>>(); | |||
_reactiveEnginesSwap = new FasterDictionary<RefWrapper<Type>, FasterList<IEngine>>(); | |||
_enginesSet = new FasterList<IEngine>(); | |||
_enginesTypeSet = new HashSet<Type>(); | |||
_disposableEngines = new FasterList<IDisposable>(); | |||
_transientEntitiesOperations = new FasterList<EntitySubmitOperation>(); | |||
_groupEntityViewsDB = new FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>>(); | |||
_groupsPerEntity = new FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>>(); | |||
_groupedEntityToAdd = new DoubleBufferedEntitiesToAdd(); | |||
_entitiesStream = new EntitiesStream(); | |||
_entitiesDB = new EntitiesDB(_groupEntityViewsDB, _groupsPerEntity, _entitiesStream); | |||
_scheduler = entityViewScheduler; | |||
_scheduler.onTick = new EntitiesSubmitter(this); | |||
} | |||
public EnginesRoot(IEntitySubmissionScheduler entityViewScheduler, bool isDeserializationOnly):this(entityViewScheduler) | |||
{ | |||
_isDeserializationOnly = isDeserializationOnly; | |||
} | |||
public void AddEngine(IEngine engine) | |||
{ | |||
var type = engine.GetType(); | |||
var refWrapper = new RefWrapper<Type>(type); | |||
DBC.ECS.Check.Require( | |||
_enginesTypeSet.Contains(refWrapper) == false || | |||
type.ContainsCustomAttribute(typeof(AllowMultipleAttribute)) == true, | |||
"The same engine has been added more than once, if intentional, use [AllowMultiple] class attribute " | |||
.FastConcat(engine.ToString())); | |||
try | |||
{ | |||
if (engine is IReactOnAddAndRemove viewEngine) | |||
CheckEntityViewsEngine(viewEngine, _reactiveEnginesAddRemove); | |||
if (engine is IReactOnSwap viewEngineSwap) | |||
CheckEntityViewsEngine(viewEngineSwap, _reactiveEnginesSwap); | |||
_enginesTypeSet.Add(refWrapper); | |||
_enginesSet.Add(engine); | |||
if (engine is IDisposable) | |||
_disposableEngines.Add(engine as IDisposable); | |||
if (engine is IQueryingEntitiesEngine queryableEntityViewEngine) | |||
{ | |||
queryableEntityViewEngine.entitiesDB = _entitiesDB; | |||
queryableEntityViewEngine.Ready(); | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
throw new ECSException("Code crashed while adding engine ".FastConcat(engine.GetType().ToString(), " "), e); | |||
} | |||
} | |||
void CheckEntityViewsEngine<T>(T engine, FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines) | |||
where T : class, IEngine | |||
{ | |||
var interfaces = engine.GetType().GetInterfaces(); | |||
foreach (var interf in interfaces) | |||
{ | |||
if (interf.IsGenericTypeEx() && typeof(T).IsAssignableFrom(interf)) | |||
{ | |||
var genericArguments = interf.GetGenericArgumentsEx(); | |||
AddEngine(engine, genericArguments, engines); | |||
} | |||
} | |||
} | |||
static void AddEngine<T>(T engine, Type[] entityViewTypes, | |||
FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines) | |||
where T : class, IEngine | |||
{ | |||
for (var i = 0; i < entityViewTypes.Length; i++) | |||
{ | |||
var type = entityViewTypes[i]; | |||
AddEngine(engine, engines, type); | |||
} | |||
} | |||
static void AddEngine<T>(T engine, FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> engines, Type type) | |||
where T : class, IEngine | |||
{ | |||
if (engines.TryGetValue(new RefWrapper<Type>(type), out var list) == false) | |||
{ | |||
list = new FasterList<IEngine>(); | |||
engines.Add(new RefWrapper<Type>(type), list); | |||
} | |||
list.Add(engine); | |||
} | |||
readonly FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> _reactiveEnginesAddRemove; | |||
readonly FasterDictionary<RefWrapper<Type>, FasterList<IEngine>> _reactiveEnginesSwap; | |||
readonly FasterList<IDisposable> _disposableEngines; | |||
readonly FasterList<IEngine> _enginesSet; | |||
readonly HashSet<Type> _enginesTypeSet; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 63f9381d3d2e91f4eb2f890bb36c605b | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,305 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot : IDisposable | |||
{ | |||
/// <summary> | |||
/// Dispose an EngineRoot once not used anymore, so that all the | |||
/// engines are notified with the entities removed. | |||
/// It's a clean up process. | |||
/// </summary> | |||
public void Dispose() | |||
{ | |||
using (var profiler = new PlatformProfiler("Final Dispose")) | |||
{ | |||
foreach (var groups in _groupEntityViewsDB) | |||
{ | |||
foreach (var entityList in groups.Value) | |||
{ | |||
entityList.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, | |||
profiler, new ExclusiveGroup.ExclusiveGroupStruct(groups.Key)); | |||
} | |||
} | |||
_groupEntityViewsDB.Clear(); | |||
_groupsPerEntity.Clear(); | |||
foreach (var engine in _disposableEngines) | |||
engine.Dispose(); | |||
_disposableEngines.Clear(); | |||
_enginesSet.Clear(); | |||
_enginesTypeSet.Clear(); | |||
_reactiveEnginesSwap.Clear(); | |||
_reactiveEnginesAddRemove.Clear(); | |||
_entitiesOperations.Clear(); | |||
_transientEntitiesOperations.Clear(); | |||
_scheduler.Dispose(); | |||
#if DEBUG && !PROFILER | |||
_idCheckers.Clear(); | |||
#endif | |||
_groupedEntityToAdd = null; | |||
_entitiesStream.Dispose(); | |||
} | |||
GC.SuppressFinalize(this); | |||
} | |||
~EnginesRoot() | |||
{ | |||
Console.LogWarning("Engines Root has been garbage collected, don't forget to call Dispose()!"); | |||
Dispose(); | |||
} | |||
///-------------------------------------------- | |||
/// | |||
public IEntityStreamConsumerFactory GenerateConsumerFactory() | |||
{ | |||
return new GenericEntityStreamConsumerFactory(this); | |||
} | |||
public IEntityFactory GenerateEntityFactory() | |||
{ | |||
return new GenericEntityFactory(this); | |||
} | |||
public IEntityFunctions GenerateEntityFunctions() | |||
{ | |||
return new GenericEntityFunctions(this); | |||
} | |||
///-------------------------------------------- | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
EntityStructInitializer BuildEntity(EGID entityID, IEntityBuilder[] entitiesToBuild, | |||
IEnumerable<object> implementors = null) | |||
{ | |||
CheckAddEntityID(entityID); | |||
var dic = EntityFactory.BuildGroupedEntities(entityID, _groupedEntityToAdd, | |||
entitiesToBuild, implementors); | |||
return new EntityStructInitializer(entityID, dic); | |||
} | |||
///-------------------------------------------- | |||
void Preallocate<T>(uint groupID, uint size) where T : IEntityDescriptor, new() | |||
{ | |||
var entityViewsToBuild = EntityDescriptorTemplate<T>.descriptor.entitiesToBuild; | |||
var numberOfEntityViews = entityViewsToBuild.Length; | |||
//reserve space in the database | |||
if (_groupEntityViewsDB.TryGetValue(groupID, out var group) == false) | |||
group = _groupEntityViewsDB[groupID] = new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
for (var index = 0; index < numberOfEntityViews; index++) | |||
{ | |||
var entityViewBuilder = entityViewsToBuild[index]; | |||
var entityViewType = entityViewBuilder.GetEntityType(); | |||
var refWrapper = new RefWrapper<Type>(entityViewType); | |||
if (group.TryGetValue(refWrapper, out var dbList) == false) | |||
group[refWrapper] = entityViewBuilder.Preallocate(ref dbList, size); | |||
else | |||
dbList.SetCapacity(size); | |||
if (_groupsPerEntity.TryGetValue(refWrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[refWrapper] = | |||
new FasterDictionary<uint, ITypeSafeDictionary>(); | |||
groupedGroup[groupID] = dbList; | |||
} | |||
} | |||
///-------------------------------------------- | |||
/// | |||
void MoveEntityFromAndToEngines(IEntityBuilder[] entityBuilders, EGID fromEntityGID, EGID? toEntityGID) | |||
{ | |||
using (var sampler = new PlatformProfiler("Move Entity From Engines")) | |||
{ | |||
//for each entity view generated by the entity descriptor | |||
if (_groupEntityViewsDB.TryGetValue(fromEntityGID.groupID, out var fromGroup) == false) | |||
throw new ECSException("from group not found eid: ".FastConcat(fromEntityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(fromEntityGID.groupID)); | |||
//Check if there is an EntityInfoView linked to this entity, if so it's a DynamicEntityDescriptor! | |||
if (fromGroup.TryGetValue(new RefWrapper<Type>(EntityBuilderUtilities.ENTITY_STRUCT_INFO_VIEW), | |||
out var entityInfoViewDic) && | |||
(entityInfoViewDic as TypeSafeDictionary<EntityStructInfoView>).TryGetValue( | |||
fromEntityGID.entityID, out var entityInfoView)) | |||
MoveEntities(fromEntityGID, toEntityGID, entityInfoView.entitiesToBuild, fromGroup, sampler); | |||
//otherwise it's a normal static entity descriptor | |||
else | |||
MoveEntities(fromEntityGID, toEntityGID, entityBuilders, fromGroup, sampler); | |||
} | |||
} | |||
void MoveEntities(EGID fromEntityGID, EGID? toEntityGID, IEntityBuilder[] entitiesToMove, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, PlatformProfiler sampler) | |||
{ | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> toGroup = null; | |||
if (toEntityGID != null) | |||
{ | |||
var toGroupID = toEntityGID.Value.groupID; | |||
if (_groupEntityViewsDB.TryGetValue(toGroupID, out toGroup) == false) | |||
toGroup = _groupEntityViewsDB[toGroupID] = new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
//Add all the entities to the dictionary | |||
for (var i = 0; i < entitiesToMove.Length; i++) | |||
CopyEntityToDictionary(fromEntityGID, toEntityGID.Value, fromGroup, toGroup, | |||
entitiesToMove[i].GetEntityType()); | |||
} | |||
//call all the callbacks | |||
for (var i = 0; i < entitiesToMove.Length; i++) | |||
MoveEntityViewFromAndToEngines(fromEntityGID, toEntityGID, fromGroup, toGroup, | |||
entitiesToMove[i].GetEntityType(), sampler); | |||
//then remove all the entities from the dictionary | |||
for (var i = 0; i < entitiesToMove.Length; i++) | |||
RemoveEntityFromDictionary(fromEntityGID, fromGroup, entitiesToMove[i].GetEntityType(), sampler); | |||
} | |||
void CopyEntityToDictionary(EGID entityGID, EGID toEntityGID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> toGroup, Type entityViewType) | |||
{ | |||
var wrapper = new RefWrapper<Type>(entityViewType); | |||
if (fromGroup.TryGetValue(wrapper, out var fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(entityGID.groupID)); | |||
} | |||
#if DEBUG && !PROFILER | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
{ | |||
throw new EntityNotFoundException(entityGID, entityViewType); | |||
} | |||
#endif | |||
if (toGroup.TryGetValue(wrapper, out var toEntitiesDictionary) == false) | |||
{ | |||
toEntitiesDictionary = fromTypeSafeDictionary.Create(); | |||
toGroup.Add(wrapper, toEntitiesDictionary); | |||
} | |||
//todo: this must be unit tested properly | |||
if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[wrapper] = | |||
new FasterDictionary<uint, ITypeSafeDictionary>(); | |||
groupedGroup[toEntityGID.groupID] = toEntitiesDictionary; | |||
fromTypeSafeDictionary.AddEntityToDictionary(entityGID, toEntityGID, toEntitiesDictionary); | |||
} | |||
void MoveEntityViewFromAndToEngines(EGID entityGID, EGID? toEntityGID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> toGroup, Type entityViewType, | |||
in PlatformProfiler profiler) | |||
{ | |||
//add all the entities | |||
var refWrapper = new RefWrapper<Type>(entityViewType); | |||
if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(entityGID.groupID)); | |||
} | |||
ITypeSafeDictionary toEntitiesDictionary = null; | |||
if (toGroup != null) | |||
toEntitiesDictionary = toGroup[refWrapper]; //this is guaranteed to exist by AddEntityToDictionary | |||
#if DEBUG && !PROFILER | |||
if (fromTypeSafeDictionary.Has(entityGID.entityID) == false) | |||
throw new EntityNotFoundException(entityGID, entityViewType); | |||
#endif | |||
fromTypeSafeDictionary.MoveEntityFromEngines(entityGID, toEntityGID, | |||
toEntitiesDictionary, toEntityGID == null ? _reactiveEnginesAddRemove : _reactiveEnginesSwap, | |||
in profiler); | |||
} | |||
void RemoveEntityFromDictionary(EGID entityGID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> fromGroup, Type entityViewType, | |||
in PlatformProfiler profiler) | |||
{ | |||
var refWrapper = new RefWrapper<Type>(entityViewType); | |||
if (fromGroup.TryGetValue(refWrapper, out var fromTypeSafeDictionary) == false) | |||
{ | |||
throw new ECSException("no entities in from group eid: ".FastConcat(entityGID.entityID) | |||
.FastConcat(" group: ").FastConcat(entityGID.groupID)); | |||
} | |||
fromTypeSafeDictionary.RemoveEntityFromDictionary(entityGID, profiler); | |||
if (fromTypeSafeDictionary.Count == 0) //clean up | |||
{ | |||
//todo: this must be unit tested properly | |||
_groupsPerEntity[refWrapper].Remove(entityGID.groupID); | |||
//I don't remove the group if empty on purpose, in case it needs to be reused | |||
} | |||
} | |||
/// <summary> | |||
/// Todo: I should keep the group, but I need to mark the group as deleted for the Exist function to work | |||
/// </summary> | |||
/// <param name="groupID"></param> | |||
/// <param name="profiler"></param> | |||
void RemoveGroupAndEntitiesFromDB(uint groupID, in PlatformProfiler profiler) | |||
{ | |||
var dictionariesOfEntities = _groupEntityViewsDB[groupID]; | |||
foreach (var dictionaryOfEntities in dictionariesOfEntities) | |||
{ | |||
dictionaryOfEntities.Value.RemoveEntitiesFromEngines(_reactiveEnginesAddRemove, profiler, | |||
new ExclusiveGroup.ExclusiveGroupStruct(groupID)); | |||
var groupedGroupOfEntities = _groupsPerEntity[dictionaryOfEntities.Key]; | |||
groupedGroupOfEntities.Remove(groupID); | |||
} | |||
//careful, in this case I assume you really don't want to use this group anymore | |||
//so I remove it from the database | |||
_groupEntityViewsDB.Remove(groupID); | |||
} | |||
internal Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityStruct | |||
{ | |||
return _entitiesStream.GenerateConsumer<T>(name, capacity); | |||
} | |||
public Consumer<T> GenerateConsumer<T>(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, | |||
IEntityStruct | |||
{ | |||
return _entitiesStream.GenerateConsumer<T>(group, name, capacity); | |||
} | |||
//one datastructure rule them all: | |||
//split by group | |||
//split by type per group. It's possible to get all the entities of a give type T per group thanks | |||
//to the FasterDictionary capabilities OR it's possible to get a specific entityView indexed by | |||
//ID. This ID doesn't need to be the EGID, it can be just the entityID | |||
//for each group id, save a dictionary indexed by entity type of entities indexed by id | |||
//ITypeSafeDictionary = Key = entityID, Value = EntityStruct | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> _groupEntityViewsDB; | |||
//for each entity view type, return the groups (dictionary of entities indexed by entity id) where they are | |||
//found indexed by group id | |||
//EntityViewType //groupID //entityID, EntityStruct | |||
readonly FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>> _groupsPerEntity; | |||
readonly EntitiesDB _entitiesDB; | |||
readonly EntitiesStream _entitiesStream; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 40a3f08b64dbbfc42b7863e5c17636ea | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,57 @@ | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
class GenericEntityFactory : IEntityFactory | |||
{ | |||
public GenericEntityFactory(EnginesRoot weakReference) | |||
{ | |||
_enginesRoot = new WeakReference<EnginesRoot>(weakReference); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(uint entityID, | |||
ExclusiveGroup.ExclusiveGroupStruct groupStructId, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId), | |||
EntityDescriptorTemplate<T>.descriptor.entitiesToBuild, implementors); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(EGID egid, IEnumerable<object> implementors = null) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, | |||
EntityDescriptorTemplate<T>.descriptor.entitiesToBuild, implementors); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(EGID egid, T entityDescriptor, | |||
IEnumerable<object> implementors) | |||
where T : IEntityDescriptor | |||
{ | |||
return _enginesRoot.Target.BuildEntity(egid, entityDescriptor.entitiesToBuild, implementors); | |||
} | |||
public EntityStructInitializer BuildEntity<T>(uint entityID, | |||
ExclusiveGroup.ExclusiveGroupStruct groupStructId, T descriptorEntity, IEnumerable<object> implementors) | |||
where T : IEntityDescriptor | |||
{ | |||
return _enginesRoot.Target.BuildEntity(new EGID(entityID, groupStructId), | |||
descriptorEntity.entitiesToBuild, | |||
implementors); | |||
} | |||
public void PreallocateEntitySpace<T>(ExclusiveGroup.ExclusiveGroupStruct groupStructId, uint size) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.Preallocate<T>(groupStructId, size); | |||
} | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
readonly WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: c6b2d3dc41496964f91001a6f66be98a | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,130 @@ | |||
using System.Diagnostics; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
/// <summary> | |||
/// todo: EnginesRoot was a weakreference to give the change to inject | |||
/// entityfunctions from other engines root. It probably should be reverted | |||
/// </summary> | |||
sealed class GenericEntityFunctions : IEntityFunctions | |||
{ | |||
internal GenericEntityFunctions(EnginesRoot weakReference) | |||
{ | |||
_enginesRoot = new WeakReference<EnginesRoot>(weakReference); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(uint entityID, ExclusiveGroup.ExclusiveGroupStruct groupID) where T : | |||
IEntityDescriptor, new() | |||
{ | |||
RemoveEntity<T>(new EGID(entityID, groupID)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveEntity<T>(EGID entityEGID) where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.CheckRemoveEntityID(entityEGID); | |||
_enginesRoot.Target.QueueEntitySubmitOperation<T>( | |||
new EntitySubmitOperation(EntitySubmitOperationType.Remove, entityEGID, entityEGID, | |||
EntityDescriptorTemplate<T>.descriptor.entitiesToBuild)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void RemoveGroupAndEntities(ExclusiveGroup.ExclusiveGroupStruct groupID) | |||
{ | |||
_enginesRoot.Target.RemoveGroupID(groupID); | |||
_enginesRoot.Target.QueueEntitySubmitOperation( | |||
new EntitySubmitOperation(EntitySubmitOperationType.RemoveGroup, new EGID(0, groupID), new EGID())); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(uint entityID, ExclusiveGroup.ExclusiveGroupStruct fromGroupID, | |||
ExclusiveGroup.ExclusiveGroupStruct toGroupID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(new EGID(entityID, fromGroupID), toGroupID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
SwapEntityGroup<T>(fromID, new EGID(fromID.entityID, (uint) toGroupID)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, ExclusiveGroup.ExclusiveGroupStruct toGroupID | |||
, ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (fromID.groupID != mustBeFromGroup) | |||
throw new ECSException("Entity is not coming from the expected group"); | |||
SwapEntityGroup<T>(fromID, toGroupID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID | |||
, ExclusiveGroup.ExclusiveGroupStruct mustBeFromGroup) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
if (fromID.groupID != mustBeFromGroup) | |||
throw new ECSException("Entity is not coming from the expected group"); | |||
SwapEntityGroup<T>(fromID, toID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void SwapEntityGroup<T>(EGID fromID, EGID toID) | |||
where T : IEntityDescriptor, new() | |||
{ | |||
_enginesRoot.Target.CheckRemoveEntityID(fromID); | |||
_enginesRoot.Target.CheckAddEntityID(toID); | |||
_enginesRoot.Target.QueueEntitySubmitOperation<T>( | |||
new EntitySubmitOperation(EntitySubmitOperationType.Swap, | |||
fromID, toID, EntityDescriptorTemplate<T>.descriptor.entitiesToBuild)); | |||
} | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
readonly WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
void QueueEntitySubmitOperation(EntitySubmitOperation entitySubmitOperation) | |||
{ | |||
#if DEBUG && !PROFILER | |||
entitySubmitOperation.trace = new StackFrame(1, true); | |||
#endif | |||
_entitiesOperations.Add((ulong) entitySubmitOperation.fromID, entitySubmitOperation); | |||
} | |||
void QueueEntitySubmitOperation<T>(EntitySubmitOperation entitySubmitOperation) where T : IEntityDescriptor | |||
{ | |||
#if DEBUG && !PROFILER | |||
entitySubmitOperation.trace = new StackFrame(1, true); | |||
if (_entitiesOperations.TryGetValue((ulong) entitySubmitOperation.fromID, out var entitySubmitedOperation)) | |||
{ | |||
if (entitySubmitedOperation != entitySubmitOperation) | |||
throw new ECSException("Only one entity operation per submission is allowed" | |||
.FastConcat(" entityViewType: ") | |||
.FastConcat(typeof(T).Name) | |||
.FastConcat(" submission type ", entitySubmitOperation.type.ToString(), | |||
" from ID: ", entitySubmitOperation.fromID.entityID.ToString()) | |||
.FastConcat(" previous operation type: ", | |||
_entitiesOperations[(ulong) entitySubmitOperation.fromID].type | |||
.ToString())); | |||
} | |||
else | |||
#endif | |||
_entitiesOperations.Set((ulong) entitySubmitOperation.fromID, entitySubmitOperation); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: dfc6ec8bc3879b04aae4ab41ecd01e63 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,162 @@ | |||
using System; | |||
using Svelto.Common; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.ECS.Schedulers; | |||
namespace Svelto.ECS | |||
{ | |||
public partial class EnginesRoot | |||
{ | |||
readonly FasterList<EntitySubmitOperation> _transientEntitiesOperations; | |||
void SubmitEntityViews() | |||
{ | |||
using (var profiler = new PlatformProfiler("Svelto.ECS - Entities Submission")) | |||
{ | |||
int iterations = 0; | |||
do | |||
{ | |||
SingleSubmission(profiler); | |||
} while ((_groupedEntityToAdd.currentEntitiesCreatedPerGroup.Count > 0 || | |||
_entitiesOperations.Count > 0) && ++iterations < 5); | |||
#if DEBUG && !PROFILER | |||
if (iterations == 5) | |||
throw new ECSException("possible circular submission detected"); | |||
#endif | |||
} | |||
} | |||
void SingleSubmission(in PlatformProfiler profiler) | |||
{ | |||
if (_entitiesOperations.Count > 0) | |||
{ | |||
using (profiler.Sample("Remove and Swap operations")) | |||
{ | |||
_transientEntitiesOperations.FastClear(); | |||
var entitySubmitOperations = _entitiesOperations.GetValuesArray(out var count); | |||
_transientEntitiesOperations.AddRange(entitySubmitOperations, count); | |||
_entitiesOperations.FastClear(); | |||
var entitiesOperations = _transientEntitiesOperations.ToArrayFast(); | |||
for (var i = 0; i < _transientEntitiesOperations.Count; i++) | |||
{ | |||
try | |||
{ | |||
switch (entitiesOperations[i].type) | |||
{ | |||
case EntitySubmitOperationType.Swap: | |||
MoveEntityFromAndToEngines(entitiesOperations[i].builders, | |||
entitiesOperations[i].fromID, | |||
entitiesOperations[i].toID); | |||
break; | |||
case EntitySubmitOperationType.Remove: | |||
MoveEntityFromAndToEngines(entitiesOperations[i].builders, | |||
entitiesOperations[i].fromID, null); | |||
break; | |||
case EntitySubmitOperationType.RemoveGroup: | |||
RemoveGroupAndEntitiesFromDB( | |||
entitiesOperations[i].fromID.groupID, profiler); | |||
break; | |||
} | |||
} | |||
catch (Exception e) | |||
{ | |||
var str = "Crash while executing Entity Operation " | |||
.FastConcat(entitiesOperations[i].type.ToString()); | |||
throw new ECSException(str.FastConcat(" ") | |||
#if DEBUG && !PROFILER | |||
.FastConcat(entitiesOperations[i].trace.ToString()) | |||
#endif | |||
, e); | |||
} | |||
} | |||
} | |||
} | |||
_groupedEntityToAdd.Swap(); | |||
if (_groupedEntityToAdd.otherEntitiesCreatedPerGroup.Count > 0) | |||
{ | |||
using (profiler.Sample("Add operations")) | |||
{ | |||
try | |||
{ | |||
AddEntityViewsToTheDBAndSuitableEngines(profiler); | |||
} | |||
finally | |||
{ | |||
using (profiler.Sample("clear operates double buffering")) | |||
{ | |||
//other can be cleared now, but let's avoid deleting the dictionary every time | |||
_groupedEntityToAdd.ClearOther(); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
void AddEntityViewsToTheDBAndSuitableEngines(in PlatformProfiler profiler) | |||
{ | |||
using (profiler.Sample("Add entities to database")) | |||
{ | |||
//each group is indexed by entity view type. for each type there is a dictionary indexed by entityID | |||
foreach (var groupOfEntitiesToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) | |||
{ | |||
var groupID = groupOfEntitiesToSubmit.Key; | |||
//if the group doesn't exist in the current DB let's create it first | |||
if (_groupEntityViewsDB.TryGetValue(groupID, out var groupDB) == false) | |||
groupDB = _groupEntityViewsDB[groupID] = | |||
new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
//add the entityViews in the group | |||
foreach (var entityViewsToSubmit in _groupedEntityToAdd.other[groupID]) | |||
{ | |||
var type = entityViewsToSubmit.Key; | |||
var typeSafeDictionary = entityViewsToSubmit.Value; | |||
var wrapper = new RefWrapper<Type>(type); | |||
if (groupDB.TryGetValue(wrapper, out var dbDic) == false) | |||
dbDic = groupDB[wrapper] = typeSafeDictionary.Create(); | |||
//Fill the DB with the entity views generate this frame. | |||
dbDic.AddEntitiesFromDictionary(typeSafeDictionary, groupID); | |||
if (_groupsPerEntity.TryGetValue(wrapper, out var groupedGroup) == false) | |||
groupedGroup = _groupsPerEntity[wrapper] = | |||
new FasterDictionary<uint, ITypeSafeDictionary>(); | |||
groupedGroup[groupID] = dbDic; | |||
} | |||
} | |||
} | |||
//then submit everything in the engines, so that the DB is up to date with all the entity views and struct | |||
//created by the entity built | |||
using (profiler.Sample("Add entities to engines")) | |||
{ | |||
foreach (var groupToSubmit in _groupedEntityToAdd.otherEntitiesCreatedPerGroup) | |||
{ | |||
var groupID = groupToSubmit.Key; | |||
var groupDB = _groupEntityViewsDB[groupID]; | |||
foreach (var entityViewsToSubmit in _groupedEntityToAdd.other[groupID]) | |||
{ | |||
var realDic = groupDB[new RefWrapper<Type>(entityViewsToSubmit.Key)]; | |||
entityViewsToSubmit.Value.AddEntitiesToEngines(_reactiveEnginesAddRemove, realDic, in profiler, | |||
new ExclusiveGroup.ExclusiveGroupStruct(groupToSubmit.Key)); | |||
} | |||
} | |||
} | |||
} | |||
DoubleBufferedEntitiesToAdd _groupedEntityToAdd; | |||
readonly IEntitySubmissionScheduler _scheduler; | |||
readonly FasterDictionary<ulong, EntitySubmitOperation> _entitiesOperations; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 52487a06d015a94438830b74e5a5d7f7 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,301 @@ | |||
#if DEBUG && !PROFILER | |||
#define ENABLE_DEBUG_FUNC | |||
#endif | |||
using System; | |||
using System.Runtime.CompilerServices; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
partial class EntitiesDB : IEntitiesDB | |||
{ | |||
internal EntitiesDB( | |||
FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> groupEntityViewsDB, | |||
FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>> groupsPerEntity, | |||
EntitiesStream entityStream) | |||
{ | |||
_groupEntityViewsDB = groupEntityViewsDB; | |||
_groupsPerEntity = groupsPerEntity; | |||
_entityStream = entityStream; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T QueryUniqueEntity<T>(ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct | |||
{ | |||
var entities = QueryEntities<T>(group, out var count); | |||
if (count == 0) | |||
throw new ECSException("Unique entity not found '".FastConcat(typeof(T).ToString()).FastConcat("'")); | |||
if (count != 1) | |||
throw new ECSException("Unique entities must be unique! '".FastConcat(typeof(T).ToString()) | |||
.FastConcat("'")); | |||
return ref entities[0]; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public ref T QueryEntity<T>(EGID entityGID) where T : struct, IEntityStruct | |||
{ | |||
T[] array; | |||
if ((array = QueryEntitiesAndIndexInternal<T>(entityGID, out var index)) != null) | |||
return ref array[index]; | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
} | |||
public ref T QueryEntity<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct | |||
{ | |||
return ref QueryEntity<T>(new EGID(id, group)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public T[] QueryEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) | |||
where T : struct, IEntityStruct | |||
{ | |||
uint group = groupStruct; | |||
count = 0; | |||
if (SafeQueryEntityDictionary(group, out TypeSafeDictionary<T> typeSafeDictionary) == false) | |||
return RetrieveEmptyEntityViewArray<T>(); | |||
return typeSafeDictionary.GetValuesArray(out count); | |||
} | |||
public EntityCollection<T> QueryEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) | |||
where T : struct, IEntityStruct | |||
{ | |||
return new EntityCollection<T>(QueryEntities<T>(groupStruct, out var count), count); | |||
} | |||
public EntityCollection<T1, T2> QueryEntities<T1, T2>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct | |||
{ | |||
return new EntityCollection<T1, T2>(QueryEntities<T1, T2>(groupStruct, out var count), count); | |||
} | |||
public EntityCollection<T1, T2, T3> QueryEntities<T1, T2, T3>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct | |||
{ | |||
return new EntityCollection<T1, T2, T3>(QueryEntities<T1, T2, T3>(groupStruct, out var count), count); | |||
} | |||
public EntityCollections<T> QueryEntities<T>(ExclusiveGroup[] groups) where T : struct, IEntityStruct | |||
{ | |||
return new EntityCollections<T>(this, groups); | |||
} | |||
public EntityCollections<T1, T2> QueryEntities<T1, T2>(ExclusiveGroup[] groups) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct | |||
{ | |||
return new EntityCollections<T1, T2>(this, groups); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public (T1[], T2[]) QueryEntities<T1, T2>(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) | |||
where T1 : struct, IEntityStruct | |||
where T2 : struct, IEntityStruct | |||
{ | |||
var T1entities = QueryEntities<T1>(groupStruct, out var countCheck); | |||
var T2entities = QueryEntities<T2>(groupStruct, out count); | |||
if (count != countCheck) | |||
{ | |||
throw new ECSException("Entity views count do not match in group. Entity 1: ' count: " | |||
.FastConcat(countCheck) | |||
.FastConcat(typeof(T1).ToString()) | |||
.FastConcat("'. Entity 2: ' count: ".FastConcat(count) | |||
.FastConcat(typeof(T2).ToString()) | |||
.FastConcat("'"))); | |||
} | |||
return (T1entities, T2entities); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public (T1[], T2[], T3[]) QueryEntities | |||
<T1, T2, T3>(ExclusiveGroup.ExclusiveGroupStruct groupStruct, out uint count) | |||
where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct where T3 : struct, IEntityStruct | |||
{ | |||
var T1entities = QueryEntities<T1>(groupStruct, out var countCheck1); | |||
var T2entities = QueryEntities<T2>(groupStruct, out var countCheck2); | |||
var T3entities = QueryEntities<T3>(groupStruct, out count); | |||
if (count != countCheck1 || count != countCheck2) | |||
throw new ECSException("Entity views count do not match in group. Entity 1: " | |||
.FastConcat(typeof(T1).ToString()).FastConcat(" count: ").FastConcat(countCheck1).FastConcat( | |||
" Entity 2: ".FastConcat(typeof(T2).ToString()) | |||
.FastConcat(" count: ").FastConcat(countCheck2) | |||
.FastConcat(" Entity 3: ".FastConcat(typeof(T3).ToString())).FastConcat(" count: ") | |||
.FastConcat(count))); | |||
return (T1entities, T2entities, T3entities); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public EGIDMapper<T> QueryMappedEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStructId) | |||
where T : struct, IEntityStruct | |||
{ | |||
if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary<T> typeSafeDictionary) == false) | |||
throw new EntityGroupNotFoundException(groupStructId, typeof(T)); | |||
EGIDMapper<T> mapper; | |||
mapper.map = typeSafeDictionary; | |||
return mapper; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryQueryMappedEntities<T>(ExclusiveGroup.ExclusiveGroupStruct groupStructId, | |||
out EGIDMapper<T> mapper) | |||
where T : struct, IEntityStruct | |||
{ | |||
mapper = default; | |||
if (SafeQueryEntityDictionary(groupStructId, out TypeSafeDictionary<T> typeSafeDictionary) == false) | |||
return false; | |||
mapper.map = typeSafeDictionary; | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public T[] QueryEntitiesAndIndex<T>(EGID entityGID, out uint index) where T : struct, IEntityStruct | |||
{ | |||
T[] array; | |||
if ((array = QueryEntitiesAndIndexInternal<T>(entityGID, out index)) != null) | |||
return array; | |||
throw new EntityNotFoundException(entityGID, typeof(T)); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryQueryEntitiesAndIndex<T>(EGID entityGid, out uint index, out T[] array) | |||
where T : struct, IEntityStruct | |||
{ | |||
if ((array = QueryEntitiesAndIndexInternal<T>(entityGid, out index)) != null) | |||
return true; | |||
return false; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public T[] QueryEntitiesAndIndex<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index) | |||
where T : struct, IEntityStruct | |||
{ | |||
return QueryEntitiesAndIndex<T>(new EGID(id, group), out index); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool TryQueryEntitiesAndIndex<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group, out uint index, | |||
out T[] array) where T : struct, IEntityStruct | |||
{ | |||
return TryQueryEntitiesAndIndex(new EGID(id, group), out index, out array); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists<T>(EGID entityGID) where T : struct, IEntityStruct | |||
{ | |||
if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary<T> casted) == false) return false; | |||
return casted != null && casted.ContainsKey(entityGID.entityID); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists<T>(uint id, ExclusiveGroup.ExclusiveGroupStruct group) where T : struct, IEntityStruct | |||
{ | |||
if (SafeQueryEntityDictionary(group, out TypeSafeDictionary<T> casted) == false) return false; | |||
return casted != null && casted.ContainsKey(id); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool Exists(ExclusiveGroup.ExclusiveGroupStruct gid) | |||
{ | |||
return _groupEntityViewsDB.ContainsKey(gid); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public bool HasAny<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct | |||
{ | |||
QueryEntities<T>(groupStruct, out var count); | |||
return count > 0; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public uint Count<T>(ExclusiveGroup.ExclusiveGroupStruct groupStruct) where T : struct, IEntityStruct | |||
{ | |||
QueryEntities<T>(groupStruct, out var count); | |||
return count; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void PublishEntityChange<T>(EGID egid) where T : unmanaged, IEntityStruct | |||
{ | |||
_entityStream.PublishEntity(ref QueryEntity<T>(egid), egid); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
T[] QueryEntitiesAndIndexInternal<T>(EGID entityGID, out uint index) where T : struct, IEntityStruct | |||
{ | |||
index = 0; | |||
if (SafeQueryEntityDictionary(entityGID.groupID, out TypeSafeDictionary<T> safeDictionary) == false) | |||
return null; | |||
if (safeDictionary.TryFindIndex(entityGID.entityID, out index) == false) | |||
return null; | |||
return safeDictionary.GetValuesArray(out _); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
bool SafeQueryEntityDictionary<T>(uint group, out TypeSafeDictionary<T> typeSafeDictionary) | |||
where T : struct, IEntityStruct | |||
{ | |||
if (UnsafeQueryEntityDictionary(group, TypeCache<T>.type, out var safeDictionary) == false) | |||
{ | |||
typeSafeDictionary = default; | |||
return false; | |||
} | |||
//return the indexes entities if they exist | |||
typeSafeDictionary = safeDictionary as TypeSafeDictionary<T>; | |||
return true; | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
internal bool UnsafeQueryEntityDictionary(uint group, Type type, out ITypeSafeDictionary typeSafeDictionary) | |||
{ | |||
//search for the group | |||
if (_groupEntityViewsDB.TryGetValue(group, out var entitiesInGroupPerType) == false) | |||
{ | |||
typeSafeDictionary = null; | |||
return false; | |||
} | |||
//search for the indexed entities in the group | |||
return entitiesInGroupPerType.TryGetValue(new RefWrapper<Type>(type), out typeSafeDictionary); | |||
} | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
static T[] RetrieveEmptyEntityViewArray<T>() | |||
{ | |||
return EmptyList<T>.emptyArray; | |||
} | |||
//grouped set of entity views, this is the standard way to handle entity views entity views are grouped per | |||
//group, then indexable per type, then indexable per EGID. however the TypeSafeDictionary can return an array of | |||
//values directly, that can be iterated over, so that is possible to iterate over all the entity views of | |||
//a specific type inside a specific group. | |||
readonly FasterDictionary<uint, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>> _groupEntityViewsDB; | |||
//needed to be able to iterate over all the entities of the same type regardless the group | |||
//may change in future | |||
readonly FasterDictionary<RefWrapper<Type>, FasterDictionary<uint, ITypeSafeDictionary>> _groupsPerEntity; | |||
readonly EntitiesStream _entityStream; | |||
static class EmptyList<T> | |||
{ | |||
internal static readonly T[] emptyArray = new T[0]; | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: cb0042f8a957c1a4d8eb8998a0e4eef9 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,138 @@ | |||
#if !DEBUG || PROFILER | |||
#define DISABLE_CHECKS | |||
using System.Diagnostics; | |||
#endif | |||
using System; | |||
using System.Reflection; | |||
namespace Svelto.ECS | |||
{ | |||
internal static class EntityBuilderUtilities | |||
{ | |||
const string MSG = "Entity Structs field and Entity View Struct components must hold value types."; | |||
#if DISABLE_CHECKS | |||
[Conditional("_CHECKS_DISABLED")] | |||
#endif | |||
public static void CheckFields(Type entityStructType, bool needsReflection, bool isStringAllowed = false) | |||
{ | |||
if (entityStructType == ENTITY_STRUCT_INFO_VIEW || | |||
entityStructType == EGIDType || | |||
entityStructType == EXCLUSIVEGROUPSTRUCTTYPE || | |||
entityStructType == SERIALIZABLE_ENTITY_STRUCT) | |||
{ | |||
return; | |||
} | |||
if (needsReflection == false) | |||
{ | |||
if (entityStructType.IsClass) | |||
{ | |||
throw new EntityStructException("EntityStructs must be structs.", entityStructType); | |||
} | |||
FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
for (var i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
FieldInfo fieldInfo = fields[i]; | |||
Type fieldType = fieldInfo.FieldType; | |||
SubCheckFields(fieldType, entityStructType, isStringAllowed); | |||
} | |||
} | |||
else | |||
{ | |||
FieldInfo[] fields = entityStructType.GetFields(BindingFlags.Public | BindingFlags.Instance); | |||
if (fields.Length < 1) | |||
{ | |||
ProcessError("Entity View Structs must hold only entity components interfaces.", entityStructType); | |||
} | |||
for (int i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
FieldInfo fieldInfo = fields[i]; | |||
if (fieldInfo.FieldType.IsInterfaceEx() == false) | |||
{ | |||
ProcessError("Entity View Structs must hold only entity components interfaces.", | |||
entityStructType); | |||
} | |||
PropertyInfo[] properties = fieldInfo.FieldType.GetProperties( | |||
BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly); | |||
for (int j = properties.Length - 1; j >= 0; --j) | |||
{ | |||
if (properties[j].PropertyType.IsGenericType) | |||
{ | |||
Type genericTypeDefinition = properties[j].PropertyType.GetGenericTypeDefinition(); | |||
if (genericTypeDefinition == DISPATCHONSETTYPE || | |||
genericTypeDefinition == DISPATCHONCHANGETYPE) | |||
{ | |||
continue; | |||
} | |||
} | |||
Type propertyType = properties[j].PropertyType; | |||
if (propertyType != STRINGTYPE) | |||
{ | |||
//for EntityViewStructs, component fields that are structs that hold strings | |||
//are allowed | |||
SubCheckFields(propertyType, entityStructType, isStringAllowed: true); | |||
} | |||
} | |||
} | |||
} | |||
} | |||
static void SubCheckFields(Type fieldType, Type entityStructType, bool isStringAllowed = false) | |||
{ | |||
if (fieldType.IsPrimitive || fieldType.IsValueType || (isStringAllowed == true && fieldType == STRINGTYPE)) | |||
{ | |||
if (fieldType.IsValueType && !fieldType.IsEnum && fieldType.IsPrimitive == false) | |||
{ | |||
CheckFields(fieldType, false, isStringAllowed); | |||
} | |||
return; | |||
} | |||
ProcessError(MSG, entityStructType, fieldType); | |||
} | |||
static void ProcessError(string message, Type entityViewType, Type fieldType = null) | |||
{ | |||
if (fieldType != null) | |||
{ | |||
throw new EntityStructException(message, entityViewType, fieldType); | |||
} | |||
throw new EntityStructException(message, entityViewType); | |||
} | |||
static readonly Type DISPATCHONCHANGETYPE = typeof(DispatchOnChange<>); | |||
static readonly Type DISPATCHONSETTYPE = typeof(DispatchOnSet<>); | |||
static readonly Type EGIDType = typeof(EGID); | |||
static readonly Type EXCLUSIVEGROUPSTRUCTTYPE = typeof(ExclusiveGroup.ExclusiveGroupStruct); | |||
static readonly Type SERIALIZABLE_ENTITY_STRUCT = typeof(SerializableEntityStruct); | |||
static readonly Type STRINGTYPE = typeof(string); | |||
internal static readonly Type ENTITY_STRUCT_INFO_VIEW = typeof(EntityStructInfoView); | |||
} | |||
public class EntityStructException : Exception | |||
{ | |||
public EntityStructException(string message, Type entityViewType, Type type) : | |||
base(message.FastConcat(" entity view: '", entityViewType.ToString(), "', field: '", type.ToString())) | |||
{ | |||
} | |||
public EntityStructException(string message, Type entityViewType) : | |||
base(message.FastConcat(" entity view: ", entityViewType.ToString())) | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: bbdea957dc4279b449608942ff29174e | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,146 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using System.Reflection; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; | |||
using Svelto.Utilities; | |||
namespace Svelto.ECS | |||
{ | |||
public class EntityBuilder<T> : IEntityBuilder where T : struct, IEntityStruct | |||
{ | |||
static class EntityView | |||
{ | |||
internal static readonly FasterList<KeyValuePair<Type, ActionCast<T>>> cachedFields; | |||
internal static readonly Dictionary<Type, Type[]> cachedTypes; | |||
#if DEBUG && !PROFILER | |||
internal static readonly Dictionary<Type, ECSTuple<object, int>> implementorsByType; | |||
#else | |||
internal static readonly Dictionary<Type, object> implementorsByType; | |||
#endif | |||
static EntityView() | |||
{ | |||
cachedFields = new FasterList<KeyValuePair<Type, ActionCast<T>>>(); | |||
var type = typeof(T); | |||
var fields = type.GetFields(BindingFlags.Public | | |||
BindingFlags.Instance); | |||
for (var i = fields.Length - 1; i >= 0; --i) | |||
{ | |||
var field = fields[i]; | |||
var setter = FastInvoke<T>.MakeSetter(field); | |||
cachedFields.Add(new KeyValuePair<Type, ActionCast<T>>(field.FieldType, setter)); | |||
} | |||
cachedTypes = new Dictionary<Type, Type[]>(); | |||
#if DEBUG && !PROFILER | |||
implementorsByType = new Dictionary<Type, ECSTuple<object, int>>(); | |||
#else | |||
implementorsByType = new Dictionary<Type, object>(); | |||
#endif | |||
} | |||
internal static void InitCache() | |||
{} | |||
internal static void BuildEntityView(out T entityView) | |||
{ | |||
entityView = new T(); | |||
} | |||
} | |||
public EntityBuilder() | |||
{ | |||
_initializer = DEFAULT_IT; | |||
EntityBuilderUtilities.CheckFields(ENTITY_VIEW_TYPE, NEEDS_REFLECTION); | |||
if (NEEDS_REFLECTION) | |||
EntityView.InitCache(); | |||
} | |||
public EntityBuilder(in T initializer) : this() | |||
{ | |||
_initializer = initializer; | |||
} | |||
public void BuildEntityAndAddToList(ref ITypeSafeDictionary dictionary, EGID egid, | |||
IEnumerable<object> implementors) | |||
{ | |||
if (dictionary == null) | |||
dictionary = new TypeSafeDictionary<T>(); | |||
var castedDic = dictionary as TypeSafeDictionary<T>; | |||
if (NEEDS_REFLECTION) | |||
{ | |||
DBC.ECS.Check.Require(implementors != null, | |||
$"Implementors not found while building an EntityView `{typeof(T)}`"); | |||
DBC.ECS.Check.Require(castedDic.ContainsKey(egid.entityID) == false, | |||
$"building an entity with already used entity id! id: '{(ulong) egid}', {ENTITY_VIEW_NAME}"); | |||
EntityView.BuildEntityView(out var entityView); | |||
this.FillEntityView(ref entityView, entityViewBlazingFastReflection, implementors, | |||
EntityView.implementorsByType, EntityView.cachedTypes); | |||
castedDic.Add(egid.entityID, entityView); | |||
} | |||
else | |||
{ | |||
DBC.ECS.Check.Require(!castedDic.ContainsKey(egid.entityID), | |||
$"building an entity with already used entity id! id: '{egid.entityID}'"); | |||
castedDic.Add(egid.entityID, _initializer); | |||
} | |||
} | |||
ITypeSafeDictionary IEntityBuilder.Preallocate(ref ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
return Preallocate(ref dictionary, size); | |||
} | |||
static ITypeSafeDictionary Preallocate(ref ITypeSafeDictionary dictionary, uint size) | |||
{ | |||
if (dictionary == null) | |||
dictionary = new TypeSafeDictionary<T>(size); | |||
else | |||
dictionary.SetCapacity(size); | |||
return dictionary; | |||
} | |||
public Type GetEntityType() | |||
{ | |||
return ENTITY_VIEW_TYPE; | |||
} | |||
static EntityBuilder() | |||
{ | |||
ENTITY_VIEW_TYPE = typeof(T); | |||
DEFAULT_IT = default; | |||
NEEDS_REFLECTION = typeof(IEntityViewStruct).IsAssignableFrom(ENTITY_VIEW_TYPE); | |||
HAS_EGID = typeof(INeedEGID).IsAssignableFrom(ENTITY_VIEW_TYPE); | |||
ENTITY_VIEW_NAME = ENTITY_VIEW_TYPE.ToString(); | |||
SetEGIDWithoutBoxing<T>.Warmup(); | |||
} | |||
readonly T _initializer; | |||
static FasterList<KeyValuePair<Type, ActionCast<T>>> entityViewBlazingFastReflection => | |||
EntityView.cachedFields; | |||
internal static readonly Type ENTITY_VIEW_TYPE; | |||
public static readonly bool HAS_EGID; | |||
static readonly T DEFAULT_IT; | |||
static readonly bool NEEDS_REFLECTION; | |||
static readonly string ENTITY_VIEW_NAME; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 18b343f034fedc946a8a1bea33d0d3b3 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
diff a/Assets/Svelto/Svelto.ECS/EntityBuilder.cs b/Assets/Svelto/Svelto.ECS/EntityBuilder.cs (rejected hunks) | |||
@@ -1,5 +1,6 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
+using DBC.ECS; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Hybrid; | |||
using Svelto.ECS.Internal; |
@@ -0,0 +1,7 @@ | |||
fileFormatVersion: 2 | |||
guid: cb0c0429aca72c945b88d01cd901f218 | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,326 @@ | |||
using System; | |||
using System.Collections; | |||
using System.Collections.Generic; | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityCollection<T> | |||
{ | |||
public EntityCollection(T[] array, uint count) | |||
{ | |||
_array = array; | |||
_count = count; | |||
} | |||
public EntityIterator GetEnumerator() | |||
{ | |||
return new EntityIterator(_array, _count); | |||
} | |||
readonly T[] _array; | |||
readonly uint _count; | |||
public struct EntityIterator : IEnumerator<T> | |||
{ | |||
public EntityIterator(T[] array, uint count) : this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
} | |||
public ref T Current => ref _array[_index]; | |||
T IEnumerator<T>.Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly T[] _array; | |||
readonly uint _count; | |||
int _index; | |||
} | |||
} | |||
public struct EntityCollection<T1, T2> | |||
{ | |||
public EntityCollection(in (T1[], T2[]) array, uint count) | |||
{ | |||
_array = array; | |||
_count = count; | |||
} | |||
public EntityIterator GetEnumerator() | |||
{ | |||
return new EntityIterator(_array, _count); | |||
} | |||
readonly (T1[], T2[]) _array; | |||
readonly uint _count; | |||
public struct EntityIterator : IEnumerator<ValueRef<T1, T2>> | |||
{ | |||
public EntityIterator((T1[], T2[]) array, uint count) : this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
} | |||
public ValueRef<T1, T2> Current => new ValueRef<T1, T2>(_array, (uint) _index); | |||
ValueRef<T1, T2> IEnumerator<ValueRef<T1, T2>>. Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly (T1[], T2[]) _array; | |||
readonly uint _count; | |||
int _index; | |||
} | |||
} | |||
public struct EntityCollection<T1, T2, T3> | |||
{ | |||
public EntityCollection(in (T1[], T2[], T3[]) array, uint count) | |||
{ | |||
_array = array; | |||
_count = count; | |||
} | |||
public EntityIterator GetEnumerator() | |||
{ | |||
return new EntityIterator(_array, _count); | |||
} | |||
readonly (T1[], T2[], T3[]) _array; | |||
readonly uint _count; | |||
public struct EntityIterator : IEnumerator<ValueRef<T1, T2, T3>> | |||
{ | |||
public EntityIterator((T1[], T2[], T3[]) array, uint count) : this() | |||
{ | |||
_array = array; | |||
_count = count; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
} | |||
public ValueRef<T1, T2, T3> Current => new ValueRef<T1, T2, T3>(_array, (uint) _index); | |||
ValueRef<T1, T2, T3> IEnumerator<ValueRef<T1, T2, T3>>.Current => throw new NotImplementedException(); | |||
object IEnumerator. Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly (T1[], T2[], T3[]) _array; | |||
readonly uint _count; | |||
int _index; | |||
} | |||
} | |||
public struct EntityCollections<T> where T : struct, IEntityStruct | |||
{ | |||
public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
} | |||
public EntityGroupsIterator GetEnumerator() | |||
{ | |||
return new EntityGroupsIterator(_db, _groups); | |||
} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
public struct EntityGroupsIterator : IEnumerator<T> | |||
{ | |||
public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
_indexGroup = -1; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (_index + 1 >= _count && ++_indexGroup < _groups.Length) | |||
{ | |||
_index = -1; | |||
_array = _db.QueryEntities<T>(_groups[_indexGroup], out _count); | |||
} | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
_indexGroup = -1; | |||
_count = 0; | |||
} | |||
public ref T Current => ref _array[_index]; | |||
T IEnumerator<T>.Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
T[] _array; | |||
uint _count; | |||
int _index; | |||
int _indexGroup; | |||
} | |||
} | |||
public struct EntityCollections<T1, T2> where T1 : struct, IEntityStruct where T2 : struct, IEntityStruct | |||
{ | |||
public EntityCollections(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
} | |||
public EntityGroupsIterator GetEnumerator() | |||
{ | |||
return new EntityGroupsIterator(_db, _groups); | |||
} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
public struct EntityGroupsIterator : IEnumerator<ValueRef<T1, T2>> | |||
{ | |||
public EntityGroupsIterator(IEntitiesDB db, ExclusiveGroup[] groups) : this() | |||
{ | |||
_db = db; | |||
_groups = groups; | |||
_indexGroup = -1; | |||
_index = -1; | |||
} | |||
public bool MoveNext() | |||
{ | |||
while (_index + 1 >= _count && ++_indexGroup < _groups.Length) | |||
{ | |||
_index = -1; | |||
var array1 = _db.QueryEntities<T1>(_groups[_indexGroup], out _count); | |||
var array2 = _db.QueryEntities<T2>(_groups[_indexGroup], out var count1); | |||
_array = (array1, array2); | |||
#if DEBUG && !PROFILER | |||
if (_count != count1) | |||
throw new ECSException("number of entities in group doesn't match"); | |||
#endif | |||
} | |||
return ++_index < _count; | |||
} | |||
public void Reset() | |||
{ | |||
_index = -1; | |||
_indexGroup = -1; | |||
var array1 = _db.QueryEntities<T1>(_groups[0], out _count); | |||
var array2 = _db.QueryEntities<T2>(_groups[0], out var count1); | |||
_array = (array1, array2); | |||
#if DEBUG && !PROFILER | |||
if (_count != count1) | |||
throw new ECSException("number of entities in group doesn't match"); | |||
#endif | |||
} | |||
public ValueRef<T1, T2> Current | |||
{ | |||
get | |||
{ | |||
var valueRef = new ValueRef<T1, T2>(_array, (uint) _index); | |||
return valueRef; | |||
} | |||
} | |||
ValueRef<T1, T2> IEnumerator<ValueRef<T1, T2>>.Current => throw new NotImplementedException(); | |||
object IEnumerator.Current => throw new NotImplementedException(); | |||
public void Dispose() {} | |||
readonly IEntitiesDB _db; | |||
readonly ExclusiveGroup[] _groups; | |||
uint _count; | |||
int _index; | |||
int _indexGroup; | |||
(T1[], T2[]) _array; | |||
} | |||
} | |||
public struct ValueRef<T1, T2> | |||
{ | |||
readonly (T1[], T2[]) array; | |||
readonly uint index; | |||
public ValueRef(in (T1[], T2[]) entity1, uint i) | |||
{ | |||
array = entity1; | |||
index = i; | |||
} | |||
public ref T1 entityStructA => ref array.Item1[index]; | |||
public ref T2 entityStructB => ref array.Item2[index]; | |||
} | |||
public struct ValueRef<T1, T2, T3> | |||
{ | |||
readonly (T1[], T2[], T3[]) array; | |||
readonly uint index; | |||
public ValueRef(in (T1[], T2[], T3[]) entity1, uint i) | |||
{ | |||
array = entity1; | |||
index = i; | |||
} | |||
public ref T1 entityStructA => ref array.Item1[index]; | |||
public ref T2 entityStructB => ref array.Item2[index]; | |||
public ref T3 entityStructC => ref array.Item3[index]; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: b477a77cde9842c4938fe4c4143d6479 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,17 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public interface IEntityDescriptor | |||
{ | |||
IEntityBuilder[] entitiesToBuild { get; } | |||
} | |||
static class EntityDescriptorTemplate<TType> where TType : IEntityDescriptor, new() | |||
{ | |||
static EntityDescriptorTemplate() | |||
{ | |||
descriptor = new TType(); | |||
} | |||
public static IEntityDescriptor descriptor { get; } | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 1ecaeae9d21e0e8458a05cf7608e782a | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,97 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
static class EntityFactory | |||
{ | |||
public static FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> BuildGroupedEntities(EGID egid, | |||
EnginesRoot.DoubleBufferedEntitiesToAdd groupEntitiesToAdd, | |||
IEntityBuilder[] entitiesToBuild, | |||
IEnumerable<object> implementors) | |||
{ | |||
var group = FetchEntityGroup(egid.groupID, groupEntitiesToAdd); | |||
BuildEntitiesAndAddToGroup(egid, group, entitiesToBuild, implementors); | |||
return group; | |||
} | |||
static FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> FetchEntityGroup(uint groupID, | |||
EnginesRoot.DoubleBufferedEntitiesToAdd groupEntityViewsByType) | |||
{ | |||
if (groupEntityViewsByType.current.TryGetValue(groupID, out var group) == false) | |||
{ | |||
group = new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>(); | |||
groupEntityViewsByType.current.Add(groupID, group); | |||
} | |||
if (groupEntityViewsByType.currentEntitiesCreatedPerGroup.TryGetValue(groupID, out var value) == false) | |||
groupEntityViewsByType.currentEntitiesCreatedPerGroup[groupID] = 0; | |||
else | |||
groupEntityViewsByType.currentEntitiesCreatedPerGroup[groupID] = value+1; | |||
return group; | |||
} | |||
static void BuildEntitiesAndAddToGroup(EGID entityID, | |||
FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group, | |||
IEntityBuilder[] entityBuilders, IEnumerable<object> implementors) | |||
{ | |||
#if DEBUG && !PROFILER | |||
HashSet<Type> types = new HashSet<Type>(); | |||
#endif | |||
InternalBuild(entityID, group, entityBuilders, implementors | |||
#if DEBUG && !PROFILER | |||
, types | |||
#endif | |||
); | |||
} | |||
static void InternalBuild(EGID entityID, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group, | |||
IEntityBuilder[] entityBuilders, IEnumerable<object> implementors | |||
#if DEBUG && !PROFILER | |||
, HashSet<Type> types | |||
#endif | |||
) | |||
{ | |||
var count = entityBuilders.Length; | |||
#if DEBUG && !PROFILER | |||
for (var index = 0; index < count; ++index) | |||
{ | |||
var entityViewType = entityBuilders[index].GetEntityType(); | |||
if (types.Contains(entityViewType)) | |||
{ | |||
throw new ECSException("EntityBuilders must be unique inside an EntityDescriptor"); | |||
} | |||
types.Add(entityViewType); | |||
} | |||
#endif | |||
for (var index = 0; index < count; ++index) | |||
{ | |||
var entityStructBuilder = entityBuilders[index]; | |||
var entityViewType = entityStructBuilder.GetEntityType(); | |||
BuildEntity(entityID, group, entityViewType, entityStructBuilder, implementors); | |||
} | |||
} | |||
static void BuildEntity(EGID entityID, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group, | |||
Type entityViewType, IEntityBuilder entityBuilder, IEnumerable<object> implementors) | |||
{ | |||
var entityViewsPoolWillBeCreated = | |||
group.TryGetValue(new RefWrapper<Type>(entityViewType), out var safeDictionary) == false; | |||
//passing the undefined entityViewsByType inside the entityViewBuilder will allow it to be created with the | |||
//correct type and casted back to the undefined list. that's how the list will be eventually of the target | |||
//type. | |||
entityBuilder.BuildEntityAndAddToList(ref safeDictionary, entityID, implementors); | |||
if (entityViewsPoolWillBeCreated) | |||
group.Add(new RefWrapper<Type>(entityViewType), safeDictionary); | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 73604bc7f8d240e4e8730f9f28705114 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,12 @@ | |||
using System; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
class EntityGroupNotFoundException : Exception | |||
{ | |||
public EntityGroupNotFoundException(uint groupId, Type type) | |||
: base("entity group not found ".FastConcat(type.ToString())) | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 4263819fdb1fe88498346ad16e7cfc08 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,11 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public struct EntityHierarchyStruct: IEntityStruct, INeedEGID | |||
{ | |||
public readonly ExclusiveGroup.ExclusiveGroupStruct parentGroup; | |||
public EntityHierarchyStruct(ExclusiveGroup group): this() { parentGroup = group; } | |||
public EGID ID { get; set; } | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: ed0c5daf03d0e29458868443df80b239 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,7 @@ | |||
namespace Svelto.ECS | |||
{ | |||
struct EntityStructInfoView: IEntityStruct | |||
{ | |||
public IEntityBuilder[] entitiesToBuild; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 9de43f2cdb7017f4cb3cd7ecf923b932 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,12 @@ | |||
using System; | |||
namespace Svelto.ECS | |||
{ | |||
public class EntityNotFoundException : Exception | |||
{ | |||
public EntityNotFoundException(EGID entityEGID, Type entityType) : base( | |||
$"entity of type '{entityType}' with ID '{entityEGID.entityID}', group '{(uint) entityEGID.groupID}' not found!") | |||
{ | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 77beec6a936d3ad4f94b89eb4a28b14f | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,163 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// Do not use this class in place of a normal polling. | |||
/// I eventually realised than in ECS no form of communication other than polling entity components can exist. | |||
/// Using groups, you can have always an optimal set of entity components to poll, so EntityStreams must be used | |||
/// only if: | |||
/// - you want to polling engine to be able to track all the entity changes happening in between polls and not | |||
/// just the current state | |||
/// - you want a thread-safe way to read entity states, which includes all the state changes and not the last | |||
/// one only | |||
/// - you want to communicate between EnginesRoots | |||
/// </summary> | |||
class EntitiesStream : IDisposable | |||
{ | |||
internal Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityStruct | |||
{ | |||
if (_streams.ContainsKey(TypeRefWrapper<T>.wrapper) == false) _streams[TypeRefWrapper<T>.wrapper] = new EntityStream<T>(); | |||
return (_streams[TypeRefWrapper<T>.wrapper] as EntityStream<T>).GenerateConsumer(name, capacity); | |||
} | |||
public Consumer<T> GenerateConsumer<T>(ExclusiveGroup group, string name, uint capacity) | |||
where T : unmanaged, IEntityStruct | |||
{ | |||
if (_streams.ContainsKey(TypeRefWrapper<T>.wrapper) == false) _streams[TypeRefWrapper<T>.wrapper] = new EntityStream<T>(); | |||
return (_streams[TypeRefWrapper<T>.wrapper] as EntityStream<T>).GenerateConsumer(group, name, capacity); | |||
} | |||
internal void PublishEntity<T>(ref T entity, EGID egid) where T : unmanaged, IEntityStruct | |||
{ | |||
if (_streams.TryGetValue(TypeRefWrapper<T>.wrapper, out var typeSafeStream)) | |||
(typeSafeStream as EntityStream<T>).PublishEntity(ref entity, egid); | |||
else | |||
Console.LogDebug("No Consumers are waiting for this entity to change ", typeof(T)); | |||
} | |||
readonly ThreadSafeDictionary<RefWrapper<Type>, ITypeSafeStream> _streams = | |||
new ThreadSafeDictionary<RefWrapper<Type>, ITypeSafeStream>(); | |||
public void Dispose() | |||
{ | |||
_streams.Clear(); | |||
} | |||
} | |||
interface ITypeSafeStream | |||
{} | |||
class EntityStream<T> : ITypeSafeStream where T : unmanaged, IEntityStruct | |||
{ | |||
public void PublishEntity(ref T entity, EGID egid) | |||
{ | |||
for (int i = 0; i < _consumers.Count; i++) | |||
{ | |||
if (_consumers[i]._hasGroup) | |||
{ | |||
if (egid.groupID == _consumers[i]._group) | |||
{ | |||
_consumers[i].Enqueue(entity, egid); | |||
} | |||
} | |||
else | |||
{ | |||
_consumers[i].Enqueue(entity, egid); | |||
} | |||
} | |||
} | |||
public Consumer<T> GenerateConsumer(string name, uint capacity) | |||
{ | |||
var consumer = new Consumer<T>(name, capacity, this); | |||
_consumers.Add(consumer); | |||
return consumer; | |||
} | |||
public Consumer<T> GenerateConsumer(ExclusiveGroup group, string name, uint capacity) | |||
{ | |||
var consumer = new Consumer<T>(group, name, capacity, this); | |||
_consumers.Add(consumer); | |||
return consumer; | |||
} | |||
public void RemoveConsumer(Consumer<T> consumer) | |||
{ | |||
_consumers.UnorderedRemove(consumer); | |||
} | |||
readonly FasterListThreadSafe<Consumer<T>> _consumers = new FasterListThreadSafe<Consumer<T>>(); | |||
} | |||
public struct Consumer<T> : IDisposable where T : unmanaged, IEntityStruct | |||
{ | |||
internal Consumer(string name, uint capacity, EntityStream<T> stream):this() | |||
{ | |||
#if DEBUG && !PROFILER | |||
_name = name; | |||
#endif | |||
_ringBuffer = new RingBuffer<ValueTuple<T, EGID>>((int) capacity, | |||
#if DEBUG && !PROFILER | |||
_name | |||
#else | |||
string.Empty | |||
#endif | |||
); | |||
_stream = stream; | |||
} | |||
internal Consumer(ExclusiveGroup group, string name, uint capacity, EntityStream<T> stream) : this(name, | |||
capacity, stream) | |||
{ | |||
_group = group; | |||
_hasGroup = true; | |||
} | |||
internal void Enqueue(in T entity, in EGID egid) | |||
{ | |||
_ringBuffer.Enqueue((entity, egid)); | |||
} | |||
public bool TryDequeue(out T entity) | |||
{ | |||
var tryDequeue = _ringBuffer.TryDequeue(out var values); | |||
entity = values.Item1; | |||
return tryDequeue; | |||
} | |||
public bool TryDequeue(out T entity, out EGID id) | |||
{ | |||
var tryDequeue = _ringBuffer.TryDequeue(out var values); | |||
entity = values.Item1; | |||
id = values.Item2; | |||
return tryDequeue; | |||
} | |||
public void Flush() { _ringBuffer.Reset(); } | |||
public void Dispose() { _stream.RemoveConsumer(this); } | |||
public uint Count() { return (uint) _ringBuffer.Count; } | |||
readonly RingBuffer<ValueTuple<T, EGID>> _ringBuffer; | |||
readonly EntityStream<T> _stream; | |||
internal readonly ExclusiveGroup _group; | |||
internal readonly bool _hasGroup; | |||
#if DEBUG && !PROFILER | |||
readonly string _name; | |||
#endif | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: b3ae4a042b4e3304c921fe85e830ff4c | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,75 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
namespace Svelto.ECS | |||
{ | |||
public ref struct EntityStructInitializer | |||
{ | |||
public EntityStructInitializer(EGID id, FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> group) | |||
{ | |||
_group = group; | |||
_ID = id; | |||
} | |||
public void Init<T>(T initializer) where T : struct, IEntityStruct | |||
{ | |||
if (_group.TryGetValue(new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE), | |||
out var typeSafeDictionary) == false) return; | |||
var dictionary = (TypeSafeDictionary<T>) typeSafeDictionary; | |||
if (EntityBuilder<T>.HAS_EGID) | |||
SetEGIDWithoutBoxing<T>.SetIDWithoutBoxing(ref initializer, _ID); | |||
if (dictionary.TryFindIndex(_ID.entityID, out var findElementIndex)) | |||
dictionary.GetDirectValue(findElementIndex) = initializer; | |||
} | |||
public void CopyFrom<T>(T initializer) where T : struct, IEntityStruct | |||
{ | |||
var dictionary = (TypeSafeDictionary<T>) _group[new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE)]; | |||
if (EntityBuilder<T>.HAS_EGID) | |||
SetEGIDWithoutBoxing<T>.SetIDWithoutBoxing(ref initializer, _ID); | |||
dictionary[_ID.entityID] = initializer; | |||
} | |||
public ref T GetOrCreate<T>() where T : struct, IEntityStruct | |||
{ | |||
ref var entityDictionary = ref _group.GetOrCreate(new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE) | |||
, () => new TypeSafeDictionary<T>()); | |||
var dictionary = (TypeSafeDictionary<T>) entityDictionary; | |||
return ref dictionary.GetOrCreate(_ID.entityID); | |||
} | |||
public T Get<T>() where T : struct, IEntityStruct | |||
{ | |||
return (_group[new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE)] as TypeSafeDictionary<T>)[_ID.entityID]; | |||
} | |||
public bool Has<T>() where T : struct, IEntityStruct | |||
{ | |||
if (_group.TryGetValue(new RefWrapper<Type>(EntityBuilder<T>.ENTITY_VIEW_TYPE), | |||
out var typeSafeDictionary)) | |||
{ | |||
var dictionary = (TypeSafeDictionary<T>) typeSafeDictionary; | |||
if (dictionary.ContainsKey(_ID.entityID)) | |||
return true; | |||
} | |||
return false; | |||
} | |||
public static EntityStructInitializer CreateEmptyInitializer() | |||
{ | |||
return new EntityStructInitializer(new EGID(), new FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary>()); | |||
} | |||
readonly EGID _ID; | |||
readonly FasterDictionary<RefWrapper<Type>, ITypeSafeDictionary> _group; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 5cb5eea9477898947b16db259b700950 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,9 @@ | |||
using System; | |||
namespace Svelto.ECS.Schedulers | |||
{ | |||
public interface IEntitySubmissionScheduler: IDisposable | |||
{ | |||
EnginesRoot.EntitiesSubmitter onTick { set; } | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 55fb2dc75e765c9478ff524a893e54a5 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,53 @@ | |||
using System; | |||
using System.Diagnostics; | |||
namespace Svelto.ECS | |||
{ | |||
#pragma warning disable 660,661 | |||
struct EntitySubmitOperation | |||
#pragma warning restore 660,661 | |||
: IEquatable<EntitySubmitOperation> | |||
{ | |||
public readonly EntitySubmitOperationType type; | |||
public readonly IEntityBuilder[] builders; | |||
public readonly EGID fromID; | |||
public readonly EGID toID; | |||
#if DEBUG && !PROFILER | |||
public StackFrame trace; | |||
#endif | |||
public EntitySubmitOperation(EntitySubmitOperationType operation, EGID from, EGID to, | |||
IEntityBuilder[] builders = null) | |||
{ | |||
type = operation; | |||
this.builders = builders; | |||
fromID = from; | |||
toID = to; | |||
#if DEBUG && !PROFILER | |||
trace = default; | |||
#endif | |||
} | |||
public static bool operator ==(EntitySubmitOperation obj1, EntitySubmitOperation obj2) | |||
{ | |||
return obj1.Equals(obj2); | |||
} | |||
public static bool operator !=(EntitySubmitOperation obj1, EntitySubmitOperation obj2) | |||
{ | |||
return obj1.Equals(obj2) == false; | |||
} | |||
public bool Equals(EntitySubmitOperation other) | |||
{ | |||
return type == other.type && fromID == other.fromID && toID == other.toID; | |||
} | |||
} | |||
enum EntitySubmitOperationType | |||
{ | |||
Swap, | |||
Remove, | |||
RemoveGroup | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 10e7122095a379642afacb59dda231ed | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,125 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
using Svelto.DataStructures; | |||
using Svelto.ECS.Internal; | |||
using Svelto.Utilities; | |||
namespace Svelto.ECS | |||
{ | |||
#if DEBUG && !PROFILER | |||
struct ECSTuple<T1, T2> | |||
{ | |||
public readonly T1 implementorType; | |||
public T2 numberOfImplementations; | |||
public ECSTuple(T1 implementor, T2 v) | |||
{ | |||
implementorType = implementor; | |||
numberOfImplementations = v; | |||
} | |||
} | |||
#endif | |||
static class EntityViewUtility | |||
{ | |||
public static void FillEntityView<T>(this IEntityBuilder entityBuilder | |||
, ref T entityView | |||
, FasterList<KeyValuePair<Type, ActionCast<T>>> | |||
entityViewBlazingFastReflection | |||
, IEnumerable<object> implementors, | |||
#if DEBUG && !PROFILER | |||
Dictionary<Type, ECSTuple<object, int>> implementorsByType | |||
#else | |||
Dictionary<Type, object> implementorsByType | |||
#endif | |||
, Dictionary<Type, Type[]> cachedTypes | |||
) | |||
{ | |||
//efficient way to collect the fields of every EntityViewType | |||
var setters = | |||
FasterList<KeyValuePair<Type, ActionCast<T>>>.NoVirt.ToArrayFast(entityViewBlazingFastReflection, out var count); | |||
foreach (var implementor in implementors) | |||
{ | |||
if (implementor != null) | |||
{ | |||
var type = implementor.GetType(); | |||
if (cachedTypes.TryGetValue(type, out var interfaces) == false) | |||
interfaces = cachedTypes[type] = type.GetInterfacesEx(); | |||
for (var iindex = 0; iindex < interfaces.Length; iindex++) | |||
{ | |||
var componentType = interfaces[iindex]; | |||
#if DEBUG && !PROFILER | |||
if (implementorsByType.TryGetValue(componentType, out var implementorData)) | |||
{ | |||
implementorData.numberOfImplementations++; | |||
implementorsByType[componentType] = implementorData; | |||
} | |||
else | |||
implementorsByType[componentType] = new ECSTuple<object, int>(implementor, 1); | |||
#else | |||
implementorsByType[componentType] = implementor; | |||
#endif | |||
} | |||
} | |||
#if DEBUG && !PROFILER | |||
else | |||
{ | |||
Console.Log(NULL_IMPLEMENTOR_ERROR.FastConcat(" entityView ", | |||
entityBuilder.GetEntityType().ToString())); | |||
} | |||
#endif | |||
} | |||
for (var i = 0; i < count; i++) | |||
{ | |||
var fieldSetter = setters[i]; | |||
var fieldType = fieldSetter.Key; | |||
#if DEBUG && !PROFILER | |||
ECSTuple<object, int> component; | |||
#else | |||
object component; | |||
#endif | |||
if (implementorsByType.TryGetValue(fieldType, out component) == false) | |||
{ | |||
var e = new ECSException(NOT_FOUND_EXCEPTION + " Component Type: " + fieldType.Name + | |||
" - EntityView: " + entityBuilder.GetEntityType().Name); | |||
throw e; | |||
} | |||
#if DEBUG && !PROFILER | |||
if (component.numberOfImplementations > 1) | |||
throw new ECSException(DUPLICATE_IMPLEMENTOR_ERROR.FastConcat( | |||
"Component Type: ", fieldType.Name, | |||
" implementor: ", | |||
component.implementorType | |||
.ToString()) + | |||
" - EntityView: " + | |||
entityBuilder.GetEntityType().Name); | |||
#endif | |||
#if DEBUG && !PROFILER | |||
fieldSetter.Value(ref entityView, component.implementorType); | |||
#else | |||
fieldSetter.Value(ref entityView, component); | |||
#endif | |||
} | |||
implementorsByType.Clear(); | |||
} | |||
const string DUPLICATE_IMPLEMENTOR_ERROR = | |||
"<color=teal>Svelto.ECS</color> the same component is implemented with more than one implementor. This is " + | |||
"considered an error and MUST be fixed. "; | |||
const string NULL_IMPLEMENTOR_ERROR = | |||
"<color=teal>Svelto.ECS</color> Null implementor, please be careful about the implementors passed to avoid " + | |||
"performance loss "; | |||
const string NOT_FOUND_EXCEPTION = "<color=teal>Svelto.ECS</color> Implementor not found for an EntityView. "; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: d94149e37622cb44bad46dccfc619115 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,265 @@ | |||
using System; | |||
using System.Collections.Generic; | |||
#pragma warning disable 660,661 | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// Exclusive Groups guarantee that the GroupID is unique. | |||
/// | |||
/// The best way to use it is like: | |||
/// | |||
/// public static class MyExclusiveGroups //(can be as many as you want) | |||
/// { | |||
/// public static ExclusiveGroup MyExclusiveGroup1 = new ExclusiveGroup(); | |||
/// | |||
/// public static ExclusiveGroup[] GroupOfGroups = { MyExclusiveGroup1, ...}; //for each on this! | |||
/// } | |||
/// </summary> | |||
/// | |||
///use this like: | |||
/// public class TriggersGroup : ExclusiveGroup<TriggersGroup> {} | |||
public abstract class NamedExclusiveGroup<T>:ExclusiveGroup | |||
{ | |||
public static ExclusiveGroup Group = new ExclusiveGroup(); | |||
public static string name = typeof(T).FullName; | |||
public NamedExclusiveGroup() { } | |||
public NamedExclusiveGroup(string recognizeAs) : base(recognizeAs) | |||
{} | |||
public NamedExclusiveGroup(ushort range) : base(range) | |||
{} | |||
} | |||
public class ExclusiveGroup | |||
{ | |||
public ExclusiveGroup() | |||
{ | |||
_group = ExclusiveGroupStruct.Generate(); | |||
} | |||
public ExclusiveGroup(string recognizeAs) | |||
{ | |||
_group = ExclusiveGroupStruct.Generate(); | |||
_serialisedGroups.Add(recognizeAs, _group); | |||
} | |||
public ExclusiveGroup(ushort range) | |||
{ | |||
_group = new ExclusiveGroupStruct(range); | |||
#if DEBUG | |||
_range = range; | |||
#endif | |||
} | |||
public static implicit operator ExclusiveGroupStruct(ExclusiveGroup group) | |||
{ | |||
return group._group; | |||
} | |||
public static explicit operator uint(ExclusiveGroup group) | |||
{ | |||
return group._group; | |||
} | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroup a, uint b) | |||
{ | |||
#if DEBUG | |||
if (a._range == 0) | |||
throw new ECSException("adding values to a not ranged ExclusiveGroup"); | |||
if (b >= a._range) | |||
throw new ECSException("Using out of range group"); | |||
#endif | |||
return a._group + b; | |||
} | |||
readonly ExclusiveGroupStruct _group; | |||
//I use this as parameter because it must not be possible to pass null Exclusive Groups. | |||
public struct ExclusiveGroupStruct : IEquatable<ExclusiveGroupStruct>, IComparable<ExclusiveGroupStruct>, | |||
IEqualityComparer<ExclusiveGroupStruct> | |||
{ | |||
public static bool operator ==(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) | |||
{ | |||
return c1.Equals(c2); | |||
} | |||
public static bool operator !=(ExclusiveGroupStruct c1, ExclusiveGroupStruct c2) | |||
{ | |||
return c1.Equals(c2) == false; | |||
} | |||
public bool Equals(ExclusiveGroupStruct other) | |||
{ | |||
return other._id == _id; | |||
} | |||
public int CompareTo(ExclusiveGroupStruct other) | |||
{ | |||
return other._id.CompareTo(_id); | |||
} | |||
public bool Equals(ExclusiveGroupStruct x, ExclusiveGroupStruct y) | |||
{ | |||
return x._id == y._id; | |||
} | |||
public int GetHashCode(ExclusiveGroupStruct obj) | |||
{ | |||
return _id.GetHashCode(); | |||
} | |||
internal static ExclusiveGroupStruct Generate() | |||
{ | |||
ExclusiveGroupStruct groupStruct; | |||
groupStruct._id = _globalId; | |||
DBC.ECS.Check.Require(_globalId + 1 < ushort.MaxValue, "too many exclusive groups created"); | |||
_globalId++; | |||
return groupStruct; | |||
} | |||
/// <summary> | |||
/// Use this constructor to reserve N groups | |||
/// </summary> | |||
internal ExclusiveGroupStruct(ushort range) | |||
{ | |||
_id = _globalId; | |||
DBC.ECS.Check.Require(_globalId + range < ushort.MaxValue, "too many exclusive groups created"); | |||
_globalId += range; | |||
} | |||
internal ExclusiveGroupStruct(uint groupID) | |||
{ | |||
_id = groupID; | |||
} | |||
public ExclusiveGroupStruct(byte[] data, uint pos) | |||
{ | |||
_id = (uint)( | |||
data[pos++] | |||
| data[pos++] << 8 | |||
| data[pos++] << 16 | |||
| data[pos++] << 24 | |||
); | |||
DBC.ECS.Check.Ensure(_id < _globalId, "Invalid group ID deserialiased"); | |||
} | |||
public static implicit operator uint(ExclusiveGroupStruct groupStruct) | |||
{ | |||
return groupStruct._id; | |||
} | |||
public static ExclusiveGroupStruct operator+(ExclusiveGroupStruct a, uint b) | |||
{ | |||
var group = new ExclusiveGroupStruct(); | |||
group._id = a._id + b; | |||
return group; | |||
} | |||
uint _id; | |||
static uint _globalId; | |||
} | |||
public static ExclusiveGroupStruct Search(string holderGroupName) | |||
{ | |||
if (_serialisedGroups.ContainsKey(holderGroupName) == false) | |||
throw new Exception("Named Group Not Found ".FastConcat(holderGroupName)); | |||
return _serialisedGroups[holderGroupName]; | |||
} | |||
static readonly Dictionary<string, ExclusiveGroupStruct> _serialisedGroups = new Dictionary<string, | |||
ExclusiveGroupStruct>(); | |||
#if DEBUG | |||
readonly ushort _range; | |||
#endif | |||
} | |||
} | |||
#if future | |||
public static void ConstructStaticGroups() | |||
{ | |||
Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); | |||
// Assemblies or types aren't guaranteed to be returned in the same order, | |||
// and I couldn't find proof that `GetTypes()` returns them in fixed order either, | |||
// even for builds made with the exact same source code. | |||
// So will sort reflection results by name before constructing groups. | |||
var groupFields = new List<KeyValuePair<string, FieldInfo>>(); | |||
foreach (Assembly assembly in assemblies) | |||
{ | |||
Type[] types = GetTypesSafe(assembly); | |||
foreach (Type type in types) | |||
{ | |||
if (type == null || !type.IsClass) | |||
{ | |||
continue; | |||
} | |||
// Groups defined as static members in static classes | |||
if (type.IsSealed && type.IsAbstract) | |||
{ | |||
FieldInfo[] fields = type.GetFields(); | |||
foreach(var field in fields) | |||
{ | |||
if (field.IsStatic && typeof(ExclusiveGroup).IsAssignableFrom(field.FieldType)) | |||
{ | |||
groupFields.Add(new KeyValuePair<string, FieldInfo>($"{type.FullName}.{field.Name}", field)); | |||
} | |||
} | |||
} | |||
// Groups defined as classes | |||
else if (type.BaseType != null | |||
&& type.BaseType.IsGenericType | |||
&& type.BaseType.GetGenericTypeDefinition() == typeof(ExclusiveGroup<>)) | |||
{ | |||
FieldInfo field = type.GetField("Group", | |||
BindingFlags.Static | BindingFlags.Public | BindingFlags.FlattenHierarchy); | |||
groupFields.Add(new KeyValuePair<string, FieldInfo>(type.FullName, field)); | |||
} | |||
} | |||
} | |||
groupFields.Sort((a, b) => string.CompareOrdinal(a.Key, b.Key)); | |||
for (int i = 0; i < groupFields.Count; ++i) | |||
{ | |||
groupFields[i].Value.GetValue(null); | |||
#if DEBUG | |||
var group = (ExclusiveGroup) groupFields[i].Value.GetValue(null); | |||
groupNames[(uint) group] = groupFields[i].Key; | |||
#endif | |||
} | |||
} | |||
static Type[] GetTypesSafe(Assembly assembly) | |||
{ | |||
try | |||
{ | |||
Type[] types = assembly.GetTypes(); | |||
return types; | |||
} | |||
catch (ReflectionTypeLoadException e) | |||
{ | |||
return e.Types; | |||
} | |||
} | |||
#if DEBUG | |||
static string[] groupNames = new string[ushort.MaxValue]; | |||
#endif | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 8e5781b2e98b00946971f8e44b2f30be | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,61 @@ | |||
using System; | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS.Internal | |||
{ | |||
partial class EntitiesDB | |||
{ | |||
public void ExecuteOnAllEntities<T>(Action<T[], ExclusiveGroup.ExclusiveGroupStruct, uint, IEntitiesDB> action) | |||
where T : struct, IEntityStruct | |||
{ | |||
var type = typeof(T); | |||
if (_groupsPerEntity.TryGetValue(new RefWrapper<Type>(type), out var dictionary)) | |||
{ | |||
foreach (var pair in dictionary) | |||
{ | |||
var entities = (pair.Value as TypeSafeDictionary<T>).GetValuesArray(out var innerCount); | |||
if (innerCount > 0) | |||
action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this); | |||
} | |||
} | |||
} | |||
public void ExecuteOnAllEntities | |||
<T, W>(W value, Action<T[], ExclusiveGroup.ExclusiveGroupStruct, uint, IEntitiesDB, W> action) | |||
where T : struct, IEntityStruct | |||
{ | |||
var type = typeof(T); | |||
if (_groupsPerEntity.TryGetValue(new RefWrapper<Type>(type), out var dic)) | |||
{ | |||
foreach (var pair in dic) | |||
{ | |||
var entities = (pair.Value as TypeSafeDictionary<T>).GetValuesArray(out var innerCount); | |||
if (innerCount > 0) | |||
action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this, value); | |||
} | |||
} | |||
} | |||
public void ExecuteOnAllEntities | |||
<T, W>(ref W value, ExecuteOnAllEntitiesAction<T, W> action) | |||
where T : struct, IEntityStruct | |||
{ | |||
var type = typeof(T); | |||
if (_groupsPerEntity.TryGetValue(new RefWrapper<Type>(type), out var dic)) | |||
{ | |||
foreach (var pair in dic) | |||
{ | |||
var entities = (pair.Value as TypeSafeDictionary<T>).GetValuesArray(out var innerCount); | |||
if (innerCount > 0) | |||
action(entities, new ExclusiveGroup.ExclusiveGroupStruct(pair.Key), innerCount, this, ref value); | |||
} | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 41fabce3b90aa014bb62d54ab7d21ae1 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,48 @@ | |||
using System; | |||
using Svelto.ECS.Serialization; | |||
namespace Svelto.ECS | |||
{ | |||
/// <summary> | |||
/// Inherit from an ExtendibleEntityDescriptor to extend a base entity descriptor that can be used | |||
/// to swap and remove specialized entities from abstract engines | |||
/// </summary> | |||
/// <typeparam name="TType"></typeparam> | |||
public class ExtendibleEntityDescriptor<TType> : IEntityDescriptor where TType : IEntityDescriptor, new() | |||
{ | |||
static ExtendibleEntityDescriptor() | |||
{ | |||
if (typeof(ISerializableEntityDescriptor).IsAssignableFrom(typeof(TType))) | |||
throw new Exception( | |||
$"SerializableEntityDescriptors cannot be used as base entity descriptor: {typeof(TType)}"); | |||
} | |||
public ExtendibleEntityDescriptor(IEntityBuilder[] extraEntities) | |||
{ | |||
_dynamicDescriptor = new DynamicEntityDescriptor<TType>(extraEntities); | |||
} | |||
public ExtendibleEntityDescriptor() | |||
{ | |||
_dynamicDescriptor = new DynamicEntityDescriptor<TType>(true); | |||
} | |||
public ExtendibleEntityDescriptor<TType> ExtendWith<T>() where T : IEntityDescriptor, new() | |||
{ | |||
_dynamicDescriptor.ExtendWith<T>(); | |||
return this; | |||
} | |||
public ExtendibleEntityDescriptor<TType> ExtendWith(IEntityBuilder[] extraEntities) | |||
{ | |||
_dynamicDescriptor.ExtendWith(extraEntities); | |||
return this; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _dynamicDescriptor.entitiesToBuild; | |||
DynamicEntityDescriptor<TType> _dynamicDescriptor; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 87e1dec70e121e8499d5f7f2ac225394 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: db3beea83bcdd5e43b03e4014f7854f3 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,8 @@ | |||
fileFormatVersion: 2 | |||
guid: f938e83cfb62e2e47880cdca7f0db7d2 | |||
folderAsset: yes | |||
DefaultImporter: | |||
externalObjects: {} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,24 @@ | |||
#if UNITY_5 || UNITY_5_3_OR_NEWER | |||
using UnityEngine; | |||
namespace Svelto.ECS.Unity | |||
{ | |||
public abstract class GenericEntityDescriptorHolder<T>: | |||
MonoBehaviour , IEntityDescriptorHolder | |||
where T: IEntityDescriptor, new() | |||
{ | |||
public IEntityDescriptor GetDescriptor() | |||
{ | |||
return EntityDescriptorTemplate<T>.descriptor; | |||
} | |||
public string groupName => _groupName; | |||
public ushort id => _id; | |||
#pragma warning disable 649 | |||
[SerializeField] string _groupName; | |||
[SerializeField] ushort _id; | |||
#pragma warning restore 649 | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 868c4160bf062904793f49af221b1178 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,99 @@ | |||
#if UNITY_5 || UNITY_5_3_OR_NEWER | |||
using UnityEngine; | |||
namespace Svelto.ECS.Unity | |||
{ | |||
public static class SveltoGUIHelper | |||
{ | |||
public static T CreateFromPrefab<T>(ref uint startIndex, Transform contextHolder, IEntityFactory factory, | |||
ExclusiveGroup group, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder | |||
{ | |||
var holder = Create<T>(new EGID(startIndex++, group), contextHolder, factory); | |||
var childs = contextHolder.GetComponentsInChildren<IEntityDescriptorHolder>(true); | |||
foreach (var child in childs) | |||
{ | |||
if (child.GetType() != typeof(T)) | |||
{ | |||
var monoBehaviour = child as MonoBehaviour; | |||
var childImplementors = monoBehaviour.GetComponents<IImplementor>(); | |||
startIndex = InternalBuildAll( | |||
startIndex, | |||
child, | |||
factory, | |||
group, | |||
childImplementors, | |||
groupNamePostfix); | |||
} | |||
} | |||
return holder; | |||
} | |||
public static T Create<T>(EGID ID, Transform contextHolder, IEntityFactory factory) | |||
where T : MonoBehaviour, IEntityDescriptorHolder | |||
{ | |||
var holder = contextHolder.GetComponentInChildren<T>(true); | |||
DBC.ECS.Check.Assert(holder != null, $"`{nameof(holder)}` is null! No component of type " + | |||
$"`{typeof(T)}` was found between its children."); | |||
var implementors = holder.GetComponents<IImplementor>(); | |||
factory.BuildEntity(ID, holder.GetDescriptor(), implementors); | |||
return holder; | |||
} | |||
public static EntityStructInitializer CreateWithEntity<T>(EGID ID, Transform contextHolder, | |||
IEntityFactory factory, out T holder) | |||
where T : MonoBehaviour, IEntityDescriptorHolder | |||
{ | |||
holder = contextHolder.GetComponentInChildren<T>(true); | |||
var implementors = holder.GetComponents<IImplementor>(); | |||
return factory.BuildEntity(ID, holder.GetDescriptor(), implementors); | |||
} | |||
public static uint CreateAll<T>(uint startIndex, ExclusiveGroup group, | |||
Transform contextHolder, IEntityFactory factory, string groupNamePostfix = null) where T : MonoBehaviour, IEntityDescriptorHolder | |||
{ | |||
var holders = contextHolder.GetComponentsInChildren<T>(true); | |||
foreach (var holder in holders) | |||
{ | |||
var implementors = holder.GetComponents<IImplementor>(); | |||
startIndex = InternalBuildAll(startIndex, holder, factory, group, implementors, groupNamePostfix); | |||
} | |||
return startIndex; | |||
} | |||
static uint InternalBuildAll(uint startIndex, IEntityDescriptorHolder descriptorHolder, | |||
IEntityFactory factory, ExclusiveGroup group, IImplementor[] implementors, string groupNamePostfix) | |||
{ | |||
ExclusiveGroup.ExclusiveGroupStruct realGroup = group; | |||
if (string.IsNullOrEmpty(descriptorHolder.groupName) == false) | |||
{ | |||
realGroup = ExclusiveGroup.Search(!string.IsNullOrEmpty(groupNamePostfix) | |||
? $"{descriptorHolder.groupName}{groupNamePostfix}" | |||
: descriptorHolder.groupName); | |||
} | |||
EGID egid; | |||
var holderId = descriptorHolder.id; | |||
if (holderId == 0) | |||
egid = new EGID(startIndex++, realGroup); | |||
else | |||
egid = new EGID(holderId, realGroup); | |||
var init = factory.BuildEntity(egid, descriptorHolder.GetDescriptor(), implementors); | |||
init.Init(new EntityHierarchyStruct(group)); | |||
return startIndex; | |||
} | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 8dcc6bf8a648e51428a0a4c300dc0457 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,66 @@ | |||
#if UNITY_5 || UNITY_5_3_OR_NEWER | |||
using Object = UnityEngine.Object; | |||
using System; | |||
using System.Collections; | |||
using UnityEngine; | |||
namespace Svelto.ECS.Schedulers.Unity | |||
{ | |||
//The EntitySubmissionScheduler has been introduced to make the entity views submission logic platform independent | |||
//You can customize the scheduler if you wish | |||
public class UnityEntitySubmissionScheduler : IEntitySubmissionScheduler | |||
{ | |||
class Scheduler : MonoBehaviour | |||
{ | |||
public Scheduler() | |||
{ | |||
_coroutine = Coroutine(); | |||
} | |||
void Update() | |||
{ | |||
_coroutine.MoveNext(); | |||
} | |||
IEnumerator Coroutine() | |||
{ | |||
while (true) | |||
{ | |||
yield return _wait; | |||
onTick.Invoke(); | |||
} | |||
} | |||
readonly WaitForEndOfFrame _wait = new WaitForEndOfFrame(); | |||
readonly IEnumerator _coroutine; | |||
public EnginesRoot.EntitiesSubmitter onTick; | |||
} | |||
public UnityEntitySubmissionScheduler(string name = "ECSScheduler") { _name = name; } | |||
public void Dispose() | |||
{ | |||
Object.Destroy(_scheduler.gameObject); | |||
} | |||
public EnginesRoot.EntitiesSubmitter onTick | |||
{ | |||
set | |||
{ | |||
if (_scheduler == null) | |||
{ | |||
_scheduler = new GameObject(_name).AddComponent<Scheduler>(); | |||
GameObject.DontDestroyOnLoad(_scheduler.gameObject); | |||
} | |||
_scheduler.onTick = value; | |||
} | |||
} | |||
Scheduler _scheduler; | |||
readonly string _name; | |||
} | |||
} | |||
#endif |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 8f3708168c40939408e5d0f10a9e0fb5 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,104 @@ | |||
namespace Svelto.ECS | |||
{ | |||
public abstract class GenericEntityDescriptor<T> : IEntityDescriptor where T : struct, IEntityStruct | |||
{ | |||
static readonly IEntityBuilder[] _entityBuilders; | |||
static GenericEntityDescriptor() { _entityBuilders = new IEntityBuilder[] {new EntityBuilder<T>()}; } | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
} | |||
public abstract class GenericEntityDescriptor<T, U> : IEntityDescriptor | |||
where T : struct, IEntityStruct where U : struct, IEntityStruct | |||
{ | |||
static readonly IEntityBuilder[] _entityBuilders; | |||
static GenericEntityDescriptor() | |||
{ | |||
_entityBuilders = new IEntityBuilder[] {new EntityBuilder<T>(), new EntityBuilder<U>()}; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
} | |||
public abstract class GenericEntityDescriptor<T, U, V> : IEntityDescriptor | |||
where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct | |||
{ | |||
static readonly IEntityBuilder[] _entityBuilders; | |||
static GenericEntityDescriptor() | |||
{ | |||
_entityBuilders = new IEntityBuilder[] | |||
{ | |||
new EntityBuilder<T>(), | |||
new EntityBuilder<U>(), | |||
new EntityBuilder<V>() | |||
}; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
} | |||
public abstract class GenericEntityDescriptor<T, U, V, W> : IEntityDescriptor | |||
where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct | |||
where W : struct, IEntityStruct | |||
{ | |||
static readonly IEntityBuilder[] _entityBuilders; | |||
static GenericEntityDescriptor() | |||
{ | |||
_entityBuilders = new IEntityBuilder[] | |||
{ | |||
new EntityBuilder<T>(), | |||
new EntityBuilder<U>(), | |||
new EntityBuilder<V>(), | |||
new EntityBuilder<W>() | |||
}; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
} | |||
public abstract class GenericEntityDescriptor<T, U, V, W, X> : IEntityDescriptor | |||
where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct | |||
where W : struct, IEntityStruct where X : struct, IEntityStruct | |||
{ | |||
static readonly IEntityBuilder[] _entityBuilders; | |||
static GenericEntityDescriptor() | |||
{ | |||
_entityBuilders = new IEntityBuilder[] | |||
{ | |||
new EntityBuilder<T>(), | |||
new EntityBuilder<U>(), | |||
new EntityBuilder<V>(), | |||
new EntityBuilder<W>(), | |||
new EntityBuilder<X>() | |||
}; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
} | |||
public abstract class GenericEntityDescriptor<T, U, V, W, X, Y> : IEntityDescriptor | |||
where T : struct, IEntityStruct where U : struct, IEntityStruct where V : struct, IEntityStruct | |||
where W : struct, IEntityStruct where X : struct, IEntityStruct where Y : struct, IEntityStruct | |||
{ | |||
static readonly IEntityBuilder[] _entityBuilders; | |||
static GenericEntityDescriptor() | |||
{ | |||
_entityBuilders = new IEntityBuilder[] | |||
{ | |||
new EntityBuilder<T>(), | |||
new EntityBuilder<U>(), | |||
new EntityBuilder<V>(), | |||
new EntityBuilder<W>(), | |||
new EntityBuilder<X>(), | |||
new EntityBuilder<Y>() | |||
}; | |||
} | |||
public IEntityBuilder[] entitiesToBuild => _entityBuilders; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 73d7a47d7c041014c8e4cdc0c90a0671 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |
@@ -0,0 +1,33 @@ | |||
using Svelto.DataStructures; | |||
namespace Svelto.ECS | |||
{ | |||
class GenericEntityStreamConsumerFactory : IEntityStreamConsumerFactory | |||
{ | |||
public GenericEntityStreamConsumerFactory(EnginesRoot weakReference) | |||
{ | |||
_enginesRoot = new WeakReference<EnginesRoot>(weakReference); | |||
} | |||
public Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityStruct | |||
{ | |||
return _enginesRoot.Target.GenerateConsumer<T>(name, capacity); | |||
} | |||
public Consumer<T> GenerateConsumer<T>(ExclusiveGroup group, string name, uint capacity) where T : unmanaged, IEntityStruct | |||
{ | |||
return _enginesRoot.Target.GenerateConsumer<T>(group, name, capacity); | |||
} | |||
//enginesRoot is a weakreference because GenericEntityStreamConsumerFactory can be injected inside | |||
//engines of other enginesRoot | |||
readonly WeakReference<EnginesRoot> _enginesRoot; | |||
} | |||
public interface IEntityStreamConsumerFactory | |||
{ | |||
Consumer<T> GenerateConsumer<T>(string name, uint capacity) where T : unmanaged, IEntityStruct; | |||
Consumer<T> GenerateConsumer<T>(ExclusiveGroup group, string name, uint capacity) | |||
where T : unmanaged, IEntityStruct; | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
fileFormatVersion: 2 | |||
guid: 02c9be62e6bdb9a49aa9596249bb16b1 | |||
MonoImporter: | |||
externalObjects: {} | |||
serializedVersion: 2 | |||
defaultReferences: [] | |||
executionOrder: 0 | |||
icon: {instanceID: 0} | |||
userData: | |||
assetBundleName: | |||
assetBundleVariant: |