I’ve written a basic thing with two systems - one to instantiate a bunch of prefabs (trees) and another to grow them.
It works as expected and seems performant enough, but I feel uncomfortable with the implementation in a few areas and was hoping to get a little feedback. Here’s the relevant code (Unity 2019.1.8f1).
There’s some staticdataI’m instantiating inside the GrowSystem which I would like to reference in another system. What’s a conventional way of achieving this? A static class somewhere, maybe co-located with the GrowComponent declaration?
Regarding ^ static data, I’m getting an occasional runtime exception A Native Collection has not been disposed, resulting in a memory leak. Enable Full StackTraces to get more details.. I’m instantiating using Allocator.Persistent and intend for that data to stick around for the lifetime of the the program - what am I missing?
Is there a better workaroundto not being able to use Enum as a NativeHashMap key?
Thanks! And apologies in advance because I know these questions are firmly in noob land - still trying to develop intuition around C#/Unity/data oriented programming.
While I know it’s often used on this forum personally I’m against static data for initialization. I don’t think coupling your systems together is ever is a good idea; there are many disadvantages no real minimal benefits.
You still need to deallocate your array when you shutdown even if it’s persistent. The proper way would be to create it in OnCreate and destroy it in OnDestroy rather than creating it on instantiation.
Not really.
Don’t think there’s any issue in what you’re doing here. Though you could just cast it to int instead of using GetHashCode.
Random note:
You should probably just use a NativeArray instead of a NativeHashMap. You’re keys in the NativeHashMap are just a linear set of integer indices anyway.
I worked on this for a bit in the morning and just came back - took your advice for points 2 and 4.
Regarding the static/configuration data, I believe it’s necessary for this use case, but it’s also possible I’m not thinking about the problem in the ECS way yet.
I want GrowSystem to be able to handle a variety of entities of various types (trees/bushes/animals/etc) and move them through their lifecycles according to configurable rules - these rules are the static data according to which I want the system to manage its entities.
In practice though, I’m finding this difficult to achieve. Originally I tried defining the configuration data in a Dictionary of Dictionaries, but I suppose jobs require blittable data which is inconvenient for this kind of data structure. In the code’s latest form I’m using NativeHashMap since I’ve read in some other posts that these can be passed to jobs, but still WIP.
Maybe this pain indicates I should be tackling the problem differently? For instance, maybe it’s better to apply a bunch of empty tag components to the entities and register a bunch of different jobs in GrowSystem which each do exactly one thing according to the tags, rather than having just one job which performs its work based off of configuration data? Curious to hear how others approach this.
I use ICustomBootstrap and setup everything myself via constructor. Personally I prefer this workflow.
I don’t use a custom loop or worlds, I still let unity handle all the dependencies.
Looks something like this (not my exact code, I have a bit more of a complicated auto setup solution for modular packages but this is what it simplifies down to.)
public class CustomWorldInitialization : ICustomBootstrap
{
// fields
// I actually use a bit more a complicated install solution where I have my own IInstaller interface
// which is implemented on scriptable objects to provide easy configuration
// but this is just a simple example
public List<Type> Initialize(List<Type> systems)
{
var world = World.Active; // World.Active is always set before ICustomBootstrap.Initialize is called
if (this.autoGenerateTeams)
{
this.CreateDefaultTeams(world.EntityManager);
}
this.CreateObservationSystem(world, systems);
AddSystem(new VisionSystem(this.exampleField1, this.exampleField2), world, systems);
AddSystem(new RevealedSystem(this.exampleField1), world, systems);
AddSystem(new HiddenSystem(this.exampleField3), world, systems);
return systems;
}
// just realized i could probably make this generic
private static void AddSystem(ComponentSystemBase system, World world, ICollection<Type> systems)
{
world.AddSystem(system);
systems.Add(system.GetType());
}
// etc
}
By adding your system back to the list Unity will automatically handle the update loop and dependencies.
You can manually add [DisableAutoCreation] to your systems if you have some you want to auto create. Or you can just stop all your systems being created by default by adding this
// AssemblyInfo.cs
using Unity.Entities;
[assembly: DisableAutoCreation]
Like I said, I’m not claiming this is the best way and many use other methods I just think making your systems modular and independent makes everything much more maintainable and testable.
Oh interesting. Without better documentation than this and without looking at much more than your snippet, it looks like you’re populating your systems with some kind of data via this custom ECS initialization hook? I guess this is a kind of dependency injection, definitely good for testing.
Given that, I guess populating systems with initialization data is the right thing to do. Next time I have time I’ll just have to figure out how to represent my static data in blittable form so it can be passed to the system jobs.
Yeah I’m not sure ICustomBootstrap has been documented since it was added, but basically its a hook into the
DefaultWorldInitialization that lets you customize the initialize with systems, worlds etc.
Checkout DefaultWorldInitialization.cs to see how it works, but pretty much it’s used in the
public static List<Type> GetAllSystems(WorldSystemFilterFlags filterFlags)
Method, which is what unity uses to get all types for creation. Unity creates it from reflection and you can only have 1 in your project (something I hope they change as it’d make setting up 3rd party packages easier.)
// TODO: should multiple bootstrap classes be allowed?
foreach (var boot in bootstrapTypes)
{
if (bootstrap == null)
bootstrap = Activator.CreateInstance(boot) as ICustomBootstrap;
else
{
Debug.LogError("Multiple custom bootstrappers specified, ignoring " + boot);
}
}
Before calling it, Unity loops all assemblies looking for all valid systems to create, filters out that shouldn’t be auto created (those with DisableAutoCreation attribute, systems that are generic, systems that are part of the conversion system etc) and adds them to the systemTypes list.
Then the bootstrap is called
if (bootstrap != null)
{
systemTypes = bootstrap.Initialize(systemTypes);
}
Where you can setup your own systems, worlds conditionally.
Unity then loops this systemTypes list, gets or creates them (this is important as it means it’s safe to create them yourself and add to list), adds them to groups then sorts them all like usual for dependencies.
Finally had time to revisit this today, got my little demo working where static data drives behavior in the GrowSystem. Things moved quickly when I discovered that nesting native containers is not supported, though you could write your own native container which does this.
Next step will be looking at frustum/occlusion culling, though after 30 mins of reading it looks like this is non-trivial in ECS - there are some olderguidances which may be invalid due to changing APIs (idk, need to check), some more recent __ discussion __ that looks promising though complex.
As an aside, the reason I’m so focused on having systems operate on static data is because I ultimately want to write a thing that is very moddable, where as many consts as possible are defined in XLM/JSON which is read on game start; in this model, the base game I deliver will essentially be the “default mod”.
Just a warning, but static data is going to be a pretty big no-no going forward into 2019.3 as it will no longer be cleared entering play mode (if you want fast scene loading). You’ll have to manually write code to clear everything yourself.
-edit-
Also the new Hybrid Renderer package has simple culling methods you can use.
/// <summary>
/// The camera frustum planes for culling.
/// </summary>
[ReadOnly]
public NativeArray<float4> CullingPlanes;
private bool IsCulled(float3 position, float radius)
{
var result = Unity.Rendering.FrustumPlanes.Intersect(this.CullingPlanes, position, radius);
return result == Unity.Rendering.FrustumPlanes.IntersectResult.Out;
}
Or if you’re just talking about regular culling of RenderMesh it’s automatically handled for you. You can tweak the culling bounds by manually specifying the RenderBounds component otherwise it’ll just generate the bounds from the mesh.
Ah thx for the heads up. I think this is ok… the flow in my head is:
on game/scene init, some static class(es) constructors read xml/json files to populate convenient data structures in memory, stored in public static readonly fields.
Systems which need to provide this data to jobs get it on init and transform to native containers, static classes provide fns for these transformations.
In general I’d like to limit all mutable state to component data (and the odd monobehavior).