#archived-dots
1 messages ยท Page 260 of 1
i'm using a queue because as far as I know i'm only adding and removing from the end, so no point in keeping a list which is more expensive
hmm from memory a* requires something like priority queue
to order by best estimated cost
the last node you add to the queue more often than not will not be the next node you want to explore
Hmm I personally would tend to prefer byte dynamic buffers in which the write/read data is stored
Buut that's really just preference I guess
yeah so that's actually how i originally did it
just wrote to a buffer or read from a buffer
the problem was, this doesn't allow me to easily do multiple save / load requests at once
(obviously i can make more than 1 entity)
Uhh why not?
You can have a save or load buffer per entity, then you can represent the load/save operation as this entity
Or do your load/save operations require multiple arrays?
let me just have a quick think why i removed this
because this is what i originally had
i think i had some annoyances with cleaning up the memory, taking 2 frames on one of the operations depending on the ordering of systems
but maybe with some strict ownership rules
i.e.
saving - file write system responsible for cleaning up buffer
loading - save system responsible for cleaning up buffer
i can probably avoid this
because i agree, this is a much nicer approach
do need another component to provide meta data (i.e. requested save slot) but apart from that i cant think of anything else that made me change approaches before
oh yeah there was 1 thing
the giant memcpy to the buffer
(ok not that giant)
(it's only a few MB even on 150k entities)
I think it could be interesting to have your save operation as one entity and then the individual 'save sections' as child entities
So that you can avoid having one giant buffer and even do things like per 'chunk' (not necessarily entities chunk) save files
oh thats not the issue
everything is already merged to a single native list before this
the problem is subscenes
i support dynamically saveclose/openload on subscenes and this stores each subscene into their own collection
when saving the entire world though, a lot of subscenes might be closed and you can't save those (and i don't need to save those since I already have the collection of data)
so i'd need to merge all the subscene collections + general world collection into this single buffer
i can do this in a collection of jobs though so i don't think it's that problematic
can i see this?
That doesn't sound too bad to me
Will it be possible to do partial world loads/saves with your system?
yep
you can filter what you want to save
.WithAll()
.WithAny()
.WithNone()
.WithSavers() // custom savers outside of components/buffers
.SubSceneSaver() // 1 of 2 modes, either prefab (world) or subscenes
.Create();
// Also for targeted subscene saving
saveProcessor.SetSharedComponentFilter();```
so default setup is
this.saveProcessor = new SaveBuilder(this).Create();```
a subscene and just regular world processor
Niice, I was thinking in terms of world rollbacks
Another advantage of having the data in a dynamic buffer is that it's really easy to attach meta-data to the load/save operation
then to save a specific subscene
this.subSceneProcessor.SetSharedComponentFilter(new SceneTag { SceneEntity = section.SectionEntity });
just filter on it
So you could keep the last x world copies in memory and address them through some meta data
oh yeah it can totally handle rollback
i already tested this
this is just showing off sub scene save/load
but i do show just the load option
which you can clearly effectively just does a rollback
That's really cool!
I wrote a very hacky replay system on top of DOTS Netcode some time ago which I would love to cleanup or replace ๐
i really like how this turned out
with the ability to save specific components just with the [Save] attribute
but also the ability to ignore files with [SaveIgnore]
im just using the lz4 that unity provides (internally)
Unity.Core.Compression.CodecService
works in burst out of bat so i didn't have to think about it
honestly file size hasn't been much of a concern for me
150k entities was ~10MB before compression (though to be fair i was only saving 4 buffers with like 2-6 elements and 8 components)
but most people won't be saving that much anyway
Damn I can't find my numbers anymore but I initially also used LZ4 but definitely had better results with zstd
Yea in my case file size is a concern because I'm writing out 30 snapshots per second
originally i was just doing compression on final array but that takes a bit too long imo for large sets (like 8ms)
so i changed to compress per component type so i can do it multi threaded
i tested then doing a second compression and it improved size by like 20% but i dont think it's worth time so i removed it
but yeah its only getting like 50% compression atm
but the serialization doesn't require any main thread or Completes() and is fast enough I can run it in the game world on 150k entities per frame without issue
objective was to make serialization fast with deserialization being slower
though for subscenes open/load this isn't the case as the main slow part of deserialization turned out to be just creating the entities
which for subscenes you dont need to worry about
When are you applying the changes to the subscene entities?
Like is your save system applying it in that subscene staging group or will you have the 'initial' state entities for a tick or so?
for loading?
Ye
i run right after SceneSystemGroup in InitializationSystemGroup
and check every frame if the subscene has loaded so i can apply changes soon as it's there
to avoid 1 frame of entity jumping positions etc
which looked horrible until i got this timing working
Ah I found it
The 'staging' group is called ProcessAfterLoadGroup
Might be worth checking out๐คทโโ๏ธ
this only runs on subscene load though
it means i'd have to split my save system into 2
which is not the worst thing
but i didn't know ProcessAfterLoadGroup existed
so that is definitely good to know
I was just looking at the videos on your channel, the voxel engine looks nice did you take that much further?
I have a friend that's made a basic voxel engine and was saying he could probably really benefit from ecs or even jobs
ooo i dont think it progressed much more than that
that was a long time ago
I'm working on the effect system for my skill system. I have plenty of unique effect types (around 145), and one skill can apply multiple effects to a target (example: A heal effect + a dispel effect).
Currently I have a "dumb" setup:
- HealEffectComponent => HealEffectSystem
- XXXEffectComponent => XXXEffectSystem
I'm assuming there must be a better way of doing it, but I'm not entirely sure. In objects that'd be rather easy, each effect class has an execute method. But with ECS, I'm trying to think about adding a list of effects to apply to my entity, yet I'm not able to come up with a clean way of doing it
Interfaces maybe ? But how could I store a list of interfaces on my entity, since size isn't predictable
i have written a completely new voxel engine since though that isn't tied to blocks that I will probably actually keep developing at some point as I'd like to use it
did you find doing custom meshing was faster than just basically rendering a bunch of cubes?
oh much
ah really
at the time in that demo though I think I actually stuck with MeshRenderer (I had a toggle to switch between hybrid renderer v1 and MeshRenderer - i still do in my new engine for debugging.)
because it was like twice as fast
i haven't tried that demo on hbr v2 though
i was thinking, the whole dots thing is rendering a bazillion cubes so i figured that, by default it'd be prime for a voxel thing
yeah maybe v2 would make the difference as i think v1 had some issues with basic stuff right
im still not sure v2 is great for unique meshes
but it at least might be on part with regular renderer but i haven't tested
my thought was basically, if you have say 5 block types, just render a bunch of instances and hey presto, easy performance
maybe not in reality though, i just figured that was pretty much the whole idea with dots and hr
Sorry didn't mean to hijack!
No worries this is a public place lol
this is a pretty loaded topic around here with a lot of implementations floating around and there's really no best way to implement it. but just to point out something, you could argue each 'effect job' is the same as each 'effect class'
unfortunately there is still reality of 10000s of draw calls
each 32x32x32 chunk is 32768 cubes
doesn't instancing roll that into single calls though no?
from memory batches can be 1024 max so that's 32 draw calls per chunk (before you add like 4x as much from shadows/post processing)
assuming single mesh
also you're now drawing 786,432 verts per chunk
well not single but muchly reduced ๐
so at the very least you need to cull cubes that aren't visible
yeah that would def be my first thought, culling
an interesting thing i read about a while back was also hard edges when shading..
i mean this is kind of what nanite does
but even nanite is limited to 2,000,000 instances
which would only be a 125x125x125 sized world of cubes
not a very big world
so for example if you have a cube, that's 6 sides with hard edges, on the gpu each pair of triangels sharing a hard edge is split so it has double the amount of normals along the shared edge if that makes sense.. whereas with a 'smooth' shading model where ( i think unity calls them tangents right ) are shared across multiple tris it's more efficient
so i think technically having that low poly hard edged shading is more costly in terms of gpu than smooth shaded
i guess that's something that might add up over 1000's of objects
i am painfully ignorant of the gpu so i'm not going to be that helpful here
it's something i've been wanting to do a lot more work with for a while but i don't have time to generalize. i'd rather master 1 thing.
not sure if it's something that's been optimized a way in more recent drivers but it's pretty interesting
not sure if it's actually cheaper to map a single cube normal map texture over a smooth shaded cube for example, than just apply a color and make it hard shaded, if that makes any sense
yeah true, problem with being a generalist in my case, master of none ๐
I have a city builder that has procedurally generated buildings that can have variety of behaviors (in varying amounts). I can see how this can be implemented in traditional sparse storage ECS like Entt, but can't really grok the archetype version. I'm assuming this type of procedurally unique entities are undesirable with Unity ECS, right?
i would say something like that could probably still conform to ecs but i'd guess it comes down to how many of each thing you have etc.. in terms of achetypes maybe someone else could help but i'd probably think of it in terms of jobs/systems which iterate over certain groups of components.. so you could have a system that iterates all buildings with say a landing pad component and does some logic, another system that loops over all buildings with a back door, etc
Can I simply add every type of component and disable/enable them at runtime? I remember reading somewhere that is the preferred case since unity ecs doesn't really expect archetypes to change? (paraphrasing)
i think that's definitely an option.. afaik they are going to add an enable/disable flag to avoid the problem of having lots of structural changes when components/tags are added/removed often
so i do know people have recreated this manually by just having a bool in the component
so effectively you could have one building archetype, with all possible components, and it skips what it doesn't need to process
I don't know how hybrid renderer does this but I was able to push several million cubes through the renderer using draw mesh indirect
I think by default batching is 1023 instances
But if you populate a buffer and call the indirect call it can be as big as you want
yeah you'd have to do it outside of hybrid renderer
thats a pretty cool demo though
are you doing the culling on a per block basis?
Ok, I may need to return to blobbable assets. If I had a large Dictionary / Lookup table of IDs and their positions in the scene (and other scenes), currently a static dictionary, could I somehow make it into a blob asset in order for entities to be able to read and modify it?
and would Monobehaviours be able to read it as well?
this dictionary is a lot simpler than my complex structure I was trying to create when I was trying to make a dwarf fortress clone a few months ago. This is just a key (a string containing an ID), a string with a world name (basically what scene it's in), and a float3 position
well. an int2 cell position and a float3 position within that cell
both of those are blittable though
There is a Native Hashmap, but I don't yet know for certain how many IDs I need to track, so allocating it would be difficult
actually am I allowed to just do this
public struct RefIDpositionInfo
{
public FixedString32Bytes world;
public float3 positionInWorld;
}
public static class RefIDTracker
{
public static NativeHashMap<FixedString32Bytes, RefIDpositionInfo> refIDLUT = new NativeHashMap<FixedString32Bytes, RefIDpositionInfo>(1024, Allocator.Persistent);
//TODO: deserialize
public static void updateRefIDPosition(string refID, string w, float3 p)
{
if (refIDLUT.ContainsKey(refID)) //if in the table
{
refIDLUT[refID] = new RefIDpositionInfo
{
world = w,
positionInWorld = p
};
}
else //if not, add it to the table
{
refIDLUT.Add(refID, new RefIDpositionInfo
{
world = w,
positionInWorld = p
});
}
}
}
will the native hash map expand if needed?
yes
but only the non-parallel version
that said, i am concerned about this class
anynative container allocated in a field is instant red flags
how are you disposing it
Itโs persistent so Iโm not
Oh, really? Why?
you still have to dispose persistent
oh... when? when the game shuts off?
yes
perhaps it should be a singleton then
in theory it's probably fine in a windows build, but certain platforms (consoles) not disposing will cause an app crash on close and fail trcs
also you will get constant leak errors in editor
and then OnDestroy() it cleans itself up
something like this perhaps?
public sealed class RefIDTracker : MonoBehaviour
{
private static readonly RefIDTracker instance = new RefIDTracker();
public static NativeHashMap<FixedString32Bytes, RefIDpositionInfo> refIDLUT;
static RefIDTracker()
{
}
private RefIDTracker()
{
refIDLUT = new NativeHashMap<FixedString32Bytes, RefIDpositionInfo>(1024, Allocator.Persistent);
}
public static RefIDTracker Instance
{
get { return instance; }
}
public static void updateRefIDPosition(string refID, string w, float3 p)
{
if (refIDLUT.ContainsKey(refID)) //if in the table
{
refIDLUT[refID] = new RefIDpositionInfo
{
world = w,
positionInWorld = p
};
}
else //if not, add it to the table
{
refIDLUT.Add(refID, new RefIDpositionInfo
{
world = w,
positionInWorld = p
});
}
}
private void OnDestroy()
{
refIDLUT.Dispose();
}
}
if it's a singleton, that should work?
Oh right, c# has destructors, maybe I should do that instead
hey people. 0.50 broke some project functionality of mine.
i want to switch out meshes on entities at runtime
prior to 0.17 rendermesh.mesh = new Mesh(); worked
with 0.17 that did not work anymore and i switched to .SetSharedComponentData(entity, new RenderMesh { [...] });
with 0.50 that broke as well. apparently it does still change the mesh when looking in the inspector, but it won't render anything
(renderMesh.mesh.SetVertices(); etc...on the original rendermesh does still work, but is highly undesirable)
anyone know what might be the problem?
no. OnDestroy() would be the correct approach
are you trying to switch 1 entity or a bunch of them
because
.SetSharedComponentData(entity, new RenderMesh { [...] })
will only update one of them while
renderMesh.mesh.SetVertices()
would update all of them
so not exactly the same behaviour
prior to 0.17 rendermesh.mesh = new Mesh(); worked
no idea why this would have ever worked tbh, you'd only be changing the local instance
it's a terrain generator, so each chunk has it's own mesh anyway. unfortunately unity even removes mesh renderers that have no mesh assigned so i have to assign default unity mesh, e.g. cube, and then replace it with a new mesh at runtime, set the generated data, and well...push that to the rendermesh
idk, i realize that but it did work at some point so i have no idea. was in the early testing phases though and someone already pointed it out to me, probably you. all i know is that it did work at some point but it never made sense why this would've worked in the first place
unfortunately unity even removes mesh renderers that have no mesh assigned
interesting did not know that
i pool my rendermeshes in my own terrain generator
but i mark them disabled
so whatever removes them i assume wouldn't affect this
yeah if you don't assign a Mesh OR Material to a meshrenderer, all the needed renderer components will not be added to the entity
oh not using conversion?
yeah using the conversion workflow
i just convert a prefab with a material and quad
the instantiate that as needed for more instances
anyway to your problem hmm
not sure
i dont recall if i've tested my terrain gen since 0.50 has been out
only place i would do the same thing
i can go load it up see if i have same issue
the fact it's still targeting 2021 means it probably hasn't been updated ๐
in the inspector the mesh gets changed, i can double click the mesh and inspect it, fully working still, but nothing renders.
using subscenes btw
not tested without subscene
is it marked as static or something? hmm
no
btw, i can't wait for unity to support c# 10. that's gonna be so great for dots projects
instead of new RenderMesh { mesh = mesh, castShadows = renderMesh.castShadows, layer = renderMesh.layer, material = renderMesh.material, needMotionVectorPass = renderMesh.needMotionVectorPass, receiveShadows = renderMesh.receiveShadows, subMesh = renderMesh.subMesh }; you can simply write renderMesh with { mesh = mesh };
ok, so how can I read refIDLUT from this inside of a foreach in a system? even with a getter function it tells me ```The managed class type Unity.Collections.NativeHashMap2<Unity.Collections.FixedString32Bytes,IOD.Systems.RefIDpositionInfo>*is not supported. Loading from a non-readonly static fieldIOD.Systems.RefIDTracker.refIDLUT` is not supported
do I... copy it outside of the foreach loop, and pass it in?
that seems like a lot of GC work
what gc? it's a native container
or rather, a lot of allocation and disposal
ok updated my terrain system to 0.50 and i seem to have no issue
though i've realized i'm not doing SetSharedComponentData
{
var available = math.min(required, this.freeMeshQuery.CalculateEntityCount());
// If we don't have enough meshes, create new ones
for (var i = available; i < required; i++)
{
var meshEntity = this.system.EntityManager.Instantiate(this.prefab);
this.system.EntityManager.AddComponent<Disabled>(meshEntity);
var renderMesh = this.system.EntityManager.GetSharedComponentData<RenderMesh>(meshEntity);
renderMesh.mesh = new Mesh();
renderMesh.mesh.MarkDynamic();
this.system.EntityManager.SetSharedComponentData(meshEntity, renderMesh);
this.allMeshes.Add(meshEntity, renderMesh.mesh);
}
}```
I create render meshes once
then just re-use the mesh
hmm. so if I do
Update(){
NativeHashMap<blahblah> LUTdata = RefIDTracker.refIDLUT;
Entities.ForEach((ref NPCWhereabouts whereabouts) =>
{
//do something with LUTdata
}
}
it would... allow that?
yes thats fine
yeah persistent native collections are a bit weird to work with
well then i have no idea what could be wrong. maybe it's just my unity project.
(each time i open the project it complains about not finding the type UIDocument and i have to change a variable name, switch to unity, let it recompile, and it works perfectly, no errors. so maybe there's more broken on the unity level and i have to create a fresh project and import everything again)
(each time i open the project it complains about not finding the type UIDocument and i have to change a variable name, switch to unity, let it recompile, and it works perfectly, no errors. so maybe there's more broken on the unity level and i have to create a fresh project and import everything again)
just reimport the file that complains
that's my annoying fix
(though for me i have 2 files that complain in separate assemblies so i need to reimport both of them)
now it tells me that this function:
public static void updateRefIDPosition(FixedString32Bytes refID, FixedString32Bytes w, float3 p)
{
RefIDpositionInfo newData = new RefIDpositionInfo
{
world = w,
positionInWorld = p
};
if (refIDLUT.ContainsKey(refID)) refIDLUT[refID] = newData; //if in the table
else refIDLUT.Add(refID, newData); //if not, add it to the table
}
gives me this error:
The managed class type `Burst error BC1042: Unity.Collections.NativeHashMap`2<Unity.Collections.FixedString32Bytes,IOD.Systems.RefIDpositionInfo>*` is not supported. Loading from a non-readonly static field `IOD.Systems.RefIDTracker.refIDLUT` is not supported
How can it not be able to read its own field? Must I make the class unsafe?
you can't use non static readonly fields in a job
Crap... how do I work around this?
I can't mark it as readonly... I need to change it in other places
maybe I can use a get private set?
or what if I made it non static? It';s a singleton so it should still be fine
or am I able to lock mutability for a bit like rust
you can't use non static readonly fields in a job
oh
but it is static
public static NativeHashMap<FixedString32Bytes, RefIDpositionInfo> refIDLUT;
It's just... not readonly
because I need to be able to change it in other functions
its not readonly
life advice
forget the static keyword exists in entities
as in dont use it or don't consider that it's static
as in don't use it
ask yourself
'would my code work if i had 2 identical worlds running at the same time?'
if not, then you've probably done something wrong
even if it's not a feature you need
wait, so I just... encapsulate the RefIDLUT in a struct? and then, call the struct's function?
also does the fat arrow matter
Invalid managed type found for the field `t` of the struct `IOD.Mobs.AI.ECS.LUTDataCapsule`.: the type `IOD.Systems.RefIDTracker` is a managed type and is not supported
struct LUTDataCapsule
{
RefIDTracker t;
public LUTDataCapsule(RefIDTracker track) => t = track;
public void updateInfo(FixedString32Bytes s, FixedString32Bytes w, float3 p) => t.updateRefIDPosition(s, w, p);
}
Dang it.
so close yet so far
but I can;t make it unsafe since it's a singleton
aaagh
Jobs are a puzzle
no. tried that already. it's broken on a deeper level
thats a shame
So, how can I encapsulate this class? Or maybe since itโs a singleton I can tell it to refer to the instance? Iโll take another crack at it tomorrowโฆ
Class? Only through class component.
Can I autoconvert list/array of entities in a field?
When there is a singular entity field it can convert prefab into entity
But I can't make the same with multiple prefabs in array or something
in prev version there used to be helper function for that in authoring class
where you provide list of gameobjects to be converted into entities prefabs
and get list of entities back
yes it's using a raycast based culling approach
I store the blocks in a bvh that I dump to the GPU for use in a compute shader that culls the instances
while functional it's not particularly realistic for use in an actual game
that's awesome
Where can I control fixed step group?
I don't use physics package btw
I want to be able to tune amount of ticks/s, whether it runs as at all and all those options
it's literally called
Timestep
{
/// <summary>
/// Set the timestep use by this group, in seconds. The default value is 1/60 seconds.
/// This value will be clamped to the range [0.0001f ... 10.0f].
/// </summary>
public float Timestep```
under the hood it's controlled by RateManager
which you can setup on any system group
oh god, I feel so ashamed, but after not toching Unity for 2 months, I totally forgot how to do Quaternion lerp in dots
hmm, VS doesn't seem to be able to find this method
is that some kind of extension?
are you using unity.mathematics?
ah, it's math.nlerp
for quaternions?
yeah
hm
/// <param name="q1">The first quaternion.</param>
/// <param name="q2">The second quaternion.</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The spherical linear interpolation of two quaternions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion slerp(quaternion q1, quaternion q2, float t)```
where is your s?
there's only slerp?
normalized lerp
/// <remarks>
/// Prefer to use this over slerp() when you know the distance between q1 and q2 is small. This can be much
/// higher performance due to avoiding trigonometric function evaluations that occur in slerp().
/// </remarks>
/// <param name="q1">The first quaternion.</param>
/// <param name="q2">The second quaternion.</param>
/// <param name="t">The interpolation parameter.</param>
/// <returns>The normalized linear interpolation of two quaternions.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static quaternion nlerp(quaternion q1, quaternion q2, float t)```
yep, pog
smth weird. System is super simple
Entities.ForEach((ref Rotation rotation, in Translation translation, in RotationTarget target, in Maneuverability maneuverability) =>
{
float3 direction = (target.Value - translation.Value).Normalized();
direction.z = 0;
quaternion targetRot = quaternion.LookRotation(new float3(0, 0, -1f), direction);
var newRot = math.nlerp(rotation.Value, targetRot, maneuverability.Value);
rotation.Value = newRot;
}).Schedule();
but entity just doesn't exist in world space
Is look rotation doing what you expect?
Because you're not passing in a normalised up vector to start with
I do
Normalized() is my utility extension
I guess I should just move my calculations into Euler
since I only have 2D space
with 1 axis to rotate
thanks for sharing, that looks great..
tbh i kindof assumed dots/hr handled this kind of stuff out of the box, i figured it was almost the hello world of dots, spewing out a million cubes..
great job on the culling etc, seems really smooth
is there built in option for dots's quaternion to euler?
Hmmm
weird thing
when I pause fixedStep group
and unpause it
it makes all those steps through paused time
I guess, it's not really gonna work out for me
What would a long running ForEach job look like (e.g. pathfinding)? Do I just schedule the job on a custom JobHandle and save it, then use it the following frame?
Would it even work? Or would a sync point invalidate the long running component type handles?
Blob assets are immutable, you wouldn't be able to modify it
Yeah I remember that now
can you link Entities with events? Or at least, a system
Systems will work just fine with normal C# events. You can use World.Default.GetOrCreateSystem<T> to get a reference. As for something akin to event driven behaviour, check out this thread: https://forum.unity.com/threads/comparing-different-approaches-for-events-in-dots.1267775/
But then you set z=0 so if z had value it's no longer normalised was what I was getting at
Dam sleep =(
Wait. If Iโm trying to call a method in a foreach that adds to a dictionary elsewhere. It doesnโt like it, but what I could do instead is: make a temporary list of stuff I need to change, and then after the foreach I could take the temporary list and add that to the managed one
I donโt have to do any weird encapsulation workarounds
Ok, thank you. Iโll look at this later. Using events on entities would make my life much easier
ha! I did it! all the errors have gone away!
Are there known issues with entities 0.17 in unity 2021.3? everything seems to be working...
some of the debugging tools are a bit busted (it's been a while, but from memory can't change pages when inspecting buffers)
but the actual runtime code is pretty stable
Guys some one can help me with some dots compatible shader issues ๐ฆ ?
I have this error on the editor . Or maybe , need i to post this error on the shaders channel ?
but the shader is either not compatible with Hybrid Renderer V2, is missing the DOTS_INSTANCING_ON variant, or there is a problem with the DOTS_INSTANCING_ON variant.
Also i read it the documentation https://docs.unity3d.com/Manual/SRPBatcher.html , and make all the things that are in there to make my custom shader srp compatible ๐ฆ
its a pain to manually make your shader hybrid renderer friendly
they recommend to just use shadergraph which does it for you
if you really need to do it yourself, there is likely someone around here who knows how
but in general, go look at a shader from shadergraph and the stuff it sets up with you need to mimic
are there any performance/design downsides of having for e.g. a single attribute buffer component vs multiple components (assuming there would be like 50+ different attributes)
e.g.
[InternalBufferCapacity(50)]
struct Attribute : IBufferElementData
{
public float BaseValue;
public float CurrentValue;
}
vs
struct Health : IComponentData
{
public float BaseValue;
public float CurrentValue;
}
struct Mana...
The reasoning I want the single Attribute component is that it makes it much easier to author new attributes in the editor without needing to resort to code-gen.
And instead of having multiple action components like AddHealth, AddMana, etc. it could just be AddAttribute which contains an index into the buffer.
there are potential performance limitations
like you can't filter for a specific attribute
however i like the 1 attribute buffer pattern for a lot of things
that said
[InternalBufferCapacity(50)]
i'd probably just store it out of chunks
store it out of chunks?
[InternalBufferCapacity(0)]
putting a large buffer in a chunk like that really limits number of entities you can store per chunk
the way I see it is that everything with the Attribute component would have to contain all 50 attributes anyway even if they don't use them all
yeah thats fine
im considering a similar implementation
having attributes indexed to a key so i can look them up directly in the buffer
and then giving everything in the game all attributes
though i'll probably use ~5 buffers and have ~300+ attributes
so we'll see how bad this ends up being on memory
right that makes sense
go check your archetypes window
[InternalBufferCapacity(0)] vs [InternalBufferCapacity(50)]
i'd imagine in general you aren't actually iterating the entire buffer and often it'll be random accessed anyway
so putting it in a chunk isn't giving that much memory benefits anyway
likely to benefit more from other components on the entity being close
apart from that, to answer original question i think this is a fine approach
though i'm not sure how explored it has been done yet
it's definitely something i'm intending to investigate further
thank you for your wisdom 
it makes it really clean to create attributes with ScriptableObjects, and have the SO store the index into the buffer so you can reference the SO to grab the magic index during authoring
Is tehre any dots alternative of debug line gizmos?
So I can draw some simple line of vector for debugging purpose?
The easiest thing by far is to just write the hlsl in a file and expose it via custom function node
You could still write the whole shader in hlsl
And not have to deal with a graph
And just use some input nodes to feed as parameters to the function
you can just use Debug.Line
terribly slow though
ALINE (or your own library) is generally recommended
Yep even my gpu animation instancing shader is based on shader graph. I just plugged custom function from hlsl file. Doing whole by hand is quite troublesome and you don't know when something break when updating.
That's the best choice ๐
<taking notes> one day I'll look at this graphic stuff
Honestly hlsl is pretty much C
I think if you are a programmer it's easier to write the code than to think in terms of the shader graph
But shader graph gives you some nice visual debug feedback at each node if you want it
The most useful thing shader graph gives you is the setup for technical stuff like here srp batcher for the hybrid renderer etc
Writing "visual" part of the shader is not that hard
Really depends on what you are doing lol
If it's just looking up a couple of textures yea
well guys after a night of research and not sleep xd i realize that are two solutions for the SRP not compatible CG shaders ๐ฆ
- make the shader on shader graph and search for the equivalents implementations
- learn hlsl over cg and make the hlsl shader
and 3. make my best on shader graph , then copy paste the shader on a entire new unlit.shader , so can make the modifications like ZTest or ZWrite
I choose the 3 for the job xd because i dont know how is te equivalent for ZTest Greater on shader graph ๐ฆ
So i made it a portal from this tutorial https://www.youtube.com/watch?v=toQIuCtk2pI&t=628s
I'm planning my pathfinding system for my project, and just wanted to know if my approach makes sense.
I think it would be good to allow pathfinding jobs to take longer than the current frame, because I don't really need them on the frame they are requested, and I will probably have a lot of pathfinding operations running at the same time. This means I can't use any TypeHandles though, and the entity that requested the path may not even exist anymore once the pathfinding job completes.
This means I need to collect all pathfinding requests and then run my pathfinding in a normal IJobParallelFor which is scheduled on a handle separate from the system's dependency (so the job isn't autocompleted the next frame). I'm just not entirely sure how to store my result data? Could I have the result data only live inside the job, and then copy it onto a DynamicBuffer on the entity (if it still exists) once the job completes?
One thing I'm not entirely sure on how to handle is the copying back to the entity, since scheduling it isn't possible (since that would force complete the job). I was thinking of using a NativeHashMap<Entity, UnsafeList> which stores the finalized path for each entity, but I haven't worked with UnsafeList much. Can I just dereference the pointer and add it to the list? (why does NativeList.GetUnsafeList even return a *UnsafeList instead of UnsafeList?)
is there a good pattern for passing data between systems, eg system1 runs job/processes, passes to system2, system2 runs job/processes, passes to system3, etc
Entities with components? ๐
I have managed data, though eventually when it's all converted that would probably be the way
Components can hold managed data, even if it's not a super good idea
(also it means you can't use it inside of a scheduled job)
There's lots of different ways to do it, it really depends on the project. I've used GCHandle previously if I absolutely needed a managed object inside a scheduled job, but I would recommend against it.
I mean more like System1.OnUpdate() { ... system2.AddData(thing); }
What is AddData supposed to do?
If you just want to somehow modify the instance of a system in a world, you can get the system with World.GetExistingSystem<Type>
This is done immediately however, as OnUpdate runs on the main thread
(So it cannot have any results of scheduled jobs)
I cannot use NativeArray<NativeList<int3>> How can I figure it out?
I have an array with fixed size I know, but for each element, there is a list with dynamic size (unknown). Elements are added if specific conditions are satisfied
Collections cannot contain collections due to the safety system
Either use NativeArray<UnsafeList<int3>> or find some other workaround
The safety system will not inform you about race conditions or undisposed memory, so you will have to be careful
Thanks, do we have another approach?
It depends on your project. Ideally you would use entities with dynamic buffers
Unfortunately, I use only Jobs, IJobParallelFor and would not like to complicate it
Again, it depends on the project, but you can also use a NativeMultiHashmap
It will let you map multiple values to a single key, and will also dynamically allocate more size if needed.
I think UnsafeList is perfect for my scenario
because each element (list inside the array) runs in different threads but adding elements to that list is thread safe
@misty wedge thanks
NativeMultiHashmap also has a parallel writer, it will just not auto expand as long as you are writing in parallel
[ReadOnly] public NativeArray<UnsafeList<int3>> FaceArray;
I have decorated it asReadonly attribute, it is OK because of reading elements of FaceArray because list inside is writeonly (adding elements inside it)
I cannot decorate it more elegant?
FaceArray[k].Add(new int3(o, j, k));
FaceArray[k].Add(new int3(o, j + 1, k));
FaceArray[k].Add(new int3(i + 1, j + 1, k));
FaceArray[k].Add(new int3(i + 1, j, k));
k=0 I see the lines are executed but length FaceArray[0]is zero!
The problem is that you need to access the reference of your UnsafeList, b/c UnsafeList is a pointer wrapped in a struct, properties like Length are returned as copies when you access it in your NativeArray
```cs
[BurstCompile]
public struct ChunkJob : IJobParallelFor
{
private readonly int _size;
private readonly int _sizeSquared;
[ReadOnly] public NativeArray<Voxel> Voxels;
public NativeArray<UnsafeList<int3>> FaceArray;
public ChunkJob(Voxel[] voxels, int size)
{
_size = size;
_sizeSquared = _size * _size;
Voxels = new NativeArray<Voxel>(voxels, Allocator.TempJob); //TODO
FaceArray = new NativeArray<UnsafeList<int3>>(size, Allocator.TempJob); //TODO
for (var i = 0; i < FaceArray.Length; i++)
{
FaceArray[i] = new UnsafeList<int3>(32, Allocator.TempJob);
}
}
I know the size of NativeArray. It is equal to size chunk size.
There are 3 loops because voxel is 3d!
I would like to parallelize the outer loop and add faces to that specified list FaceArray[k]. Face count is unknown and depends on the chunk shape
FaceArray[k] is an UnsafeList
what you can do is write an extension for your NativeArray to read the element as a reference instead.
public static ref T ElementAt<T>(this ref NativeArray<T> collection, int i) {
unsafe {
return ref UnsafeUtility.ArrayElementAsRef(collection.GetUnsafePtr(), i);
}
}
A lot of work for simple task
thanks
To use this array of lists outside, I should convert it to an array or list List<int3> .
I believe if it was NativeList or NativeArray, it could be used directly
Are there any "gotchas" besides the absence of the safety system when storing an UnsafeList inside a component?
gotta manage the memory yourself
Because NativeArray<NativeList> does not work
Well yeah, but that's the same thing for NativeList
I'm asking because I've barely used them and wanted to know of any pitfalls
well that's really the only caveat i can think of
NativeContainers are just wrappers around the same internal data structures of their UnsafeContainer versions
I assume what you wrote a bit further up is the reason why NativeList.GetUnsafeList returns an *UnsafeList and not an UnsafeList?
Since the NativeList is what is wrapping it
Yup
I assume I can safely store a regular UnsafeList inside a component, but if I modify it I would need to pass a *UnsafeList or use ref?
Yea
What happens if you just pass a copy? The internal current index and buffer location would still exist and be writeable, but if you then add to the original list, I assume it would overwrite what you just wrote?
I imagine you mean UnsafeList* not *UnsafeList cause those are 2 separate things ๐ค
Yeah my bad
๐
Let's say you pass in a copy, you'd still manipulate the contents of the address, but if you care about 'metadata' like Length because you want to add new content, then you have a chance of overwriting the previous content
// Assume someList is a copy of an UnsafeList<T>
someList.Add(100);
someList.Add(101);
// You expect the list store 100 & 101, which is 2 elements
// In the next frame
someList.Add(102);
// You expect the list to store 3 elements, but when you debug the Length property
// you see that it stores 1 element.
// The contents of the list would actually be [102, 101], because Add() has to read
// `Length` in order to push a new element. Because you manipulated a copy of
// the struct, you don't store the metadata between frames.
Isn't part of that metadata the index location in the backing array though?
Ah it reads Length, yeah, that makes sense
What happens if the addition to the copy happened to resize the list? I'm guessing the resizing automatically invalidates the old memory location?
yea
Will it crash then?
probably - if there's something holding onto the old pointer and attempting to access it since it might be invalidated
Yeah like a copy of the unsafe list that still has the old location and you attempt to Add
yea
Alright, I think I get it
Thanks a bunch for answering all my questions, I really appreciate it!
yea np
One last thing, is there any point to storing an UnsafeList* inside the component, or is UnsafeList fine?
I guess it would keep one from accidentally copying the struct
I wanna say it depends on context, cause what I'm thinking is if you use like a ForEach lambda
struct SomeComponent : IComponentData {
public UnsafeList List;
}
Entities.ForEach((ref SomeComponent a) => {
a.List.Add(...); // This would be okay since you access SomeComponent by ref.
});
In a IJobEntityBatch
struct SoemJob : IJobEntityBatch {
...
public void Execute(...) {
NativeArray<SomeComponent> b = batchInChunk.GetNativeArray(SomeComponentTypeHandle);
for (int i = 0; i < b.Length; i++) {
b[i].List.Add(someValue); // This wouldn't work nicely, you need to access by ref
}
}
}
Yeah I guess you just gotta be really careful with all the unsafe collections
personally I tried to avoid if possible, but if it worse comes to worse, then yea I'd just store a pointer ๐ค
This might seem silly, but I'm just asking to make sure. To "use" the pointer then, I would use the "C++" way of doing it, e.g. myList->Add(...)
Barely used pointers in C#
yea
or (*myList).Add(...)
I also think myList[0].Add might also work ๐
yea gets confusing, just stick with the -> operator haha
It probably will, since it would just be (*(myList+0))
Something else I was wondering today to which I couldn't find an answer. Since afaik Unity uses a fixed number of worker threads internally when scheduling jobs, do those threads need to complete before they can be reused? Or can they "halt" during execution similar to CPU scheduling with a virtual threadcount greater than the physical thread count?`
For example, what happens if Unity has 8 worker threads, and schedules 8 extremely long running jobs, would those jobs eventually yield processor resources during execution, or would the program halt until they finish?
I think it only halts the main thread if you call complete as a sync point. You could have like a long running job by making sure that handle isn't managed in some automatic dependency chain I think. tbh I'm not really sure about if Unity's job system threads can halt and then allow you to use it for a diff job ๐ค
Yeah, I was planning to have my pathfinding jobs run "outside" of sync points by not passing the system's dependency to the job, but I'm not sure how long running jobs behave if you schedule a lot of them at once
I guess it would be easy enough to test, just schedule JobsUtility.JobWorkerCount / JobsUtility.JobWorkerMaximumCount amount of infinitely running jobs and see if it freezes completely or just gets slower
I keep data separately for each chunk. Inside each chunk, I have used IJobParallelFor to get faces. It is OK.
Now, I would like to parallelize chunks as well.
I cannot use IJobParallelFor inside jobs, right?
I have tested this approach. Add all jobhandles for all chunks in one NativeArray and call CompleteAll method. I do not notice significant boost
but CompleteAll does not utilize multi threading?
CompleteAll will simply block the main thread until all jobhandles are completed
how many path finding requests are you making per/frame that you need to run them long?
What is DOTS used for?
How do I get a box collider from an entity in DOTS
var collider = MainLoader.entityManager.GetComponentObject<Unity.Physics.BoxCollider>(icon.buildingEntity);
This throws an unknown type error
The short answer is that it makes multithreading and writing more efficient game code easier. Although since it's still early prototype it requires a lot of experience with coding and Unity to get in to.
boxcollider is not a component
PhysicsCollider is the component
it has a field called Value
public BlobAssetReference<Collider> Value;
that holds the collider info in blobasset memory
to get the actual box collider you need to use unsafe code
can you point me in the direction of how to do that
actually to avoid unsafe code you could try this
ref Collider collider = ref physicsCollider.Value.Value;
if (collider.Type == ColliderType.Box)
{
BoxCollider boxCollider = UnsafeUtility.As<Collider, BoxCollider>(ref collider);
}```
make sure you return it by ref from the blob otherwise you will only copy the header to the local value and lose all the box info then read memory out of range
Thanks!
var physicsCollider = MainLoader.entityManager.GetComponentData<PhysicsCollider>(icon.buildingEntity);
ref Unity.Physics.Collider collider = ref physicsCollider.Value.Value;
float yOffset = 0;
if (collider.Type == ColliderType.Box)
{
Unity.Physics.BoxCollider boxCollider = UnsafeUtility.As<Unity.Physics.Collider, Unity.Physics.BoxCollider>(ref collider);
yOffset = boxCollider.Size.y;
}
This worked for me
Another question if you dont mind. I am trying to do a ray cast click to select an object. This normally works fine but if a UI RawImage element is on top, the raycast doesn't collide with the object underneath. I have tried to change the CollisionFilter to only collide with the desired layer and put those objects as belonging to that layer (with the Belongs To on the Physics Shape script) but this doesnt work. I even made the objects explicitly "collides with" set to a layer that i put the RayInput "BelongsTo" on and it doesnt work. what gives?
RaycastInput RayInput = new RaycastInput
{
Start = unityRay.origin,
End = unityRay.origin + unityRay.direction * k_MaxDistance,
Filter = new CollisionFilter
{
BelongsTo = 1u << 2,
CollidesWith = 1u << 1,
GroupIndex = 0
},
};
I even put a physics shape on the UI element prefabs and made them belong to nothing, collide with nothing and no collisions at all and it doesnt work either!
well unity physics raycast wont collide with UI
are you sure this ray is firing
generally UI that triggers a raycast will try top stop sending the input event any further
if you don't want your UI to raycast the rawimage, just turn off raycast target on the rawimage?
Nevermind! I was stopping the raycast from even occuring further up in the code if the pointer was over a gameobject. silly me
it works fine now
I have a question... How do I send a commandbuffer type thing into this: https://pastebin.com/QanHcnwj
Pastebin.com is the number one paste tool since 2002. Pastebin is a website where you can store text online for a set period of time.
I'm still trying to upgrade to .5. I'm almost fully done.
I'm trying to get an EntityCommandBuffer type thing into a ICollisionEventsJob
Thanks for getting me this far. This is the last step. In .17 I could use a commandBuffer when I got the colliding objects so I could process them and change the state of the simulation. In .5, I think you can still do this, but it isn't obvious.
i don't believe anything has changed between 0.17 and 0.50 regarding command buffers
(except the query overload for removecomponent)
I'm using different collision code.
My old .17 was Jobs or something
Dependency = new CollisionEventImpulseJob
{
ed = GetComponentDataFromEntity<EntityData>(true),
td= GetComponentDataFromEntity<Translation>(true),
}.Schedule(_stepPhysicsWorldSystem.Simulation, Dependency); _endSimEntityCommandBufferSystem.AddJobHandleForProducer(Dependency);
Thats my new code. I pass in EntityData and Translation... But I am not sure how to pass in an EntityCommandBuffer or something
I can detect collisions, but really not sure how to do anything with em since I have no way of buffering EntityCommands.
I see there is an _endSimEntityCommandBufferSystem thing there, but I'm not sure if it is passed into the CollisionEventImpulseJob : ICollisionEventsJob
Its a very simple question for someone who knows... I'm just stumbling over it.
Thanks everyone for getting me this far. I have complete confidence someone will help me finally get a stable compile sometime. I'll just code around it in other parts of my projects. One key thing for game development is to never let a brick wall discourage you. Keep working at other parts.
so I randomly stumbled upon a fix for this
make a build (I only tested via build configurations)
as soon as I made a build, anytime I open unity in my project I no longer have the issue with compile errors
i tested this on 2 other projects that also had the issue and it fixed both of them as well
I'm just thinking about how to go about it. You think it would be better to just finish them each frame?
depends entirely how many you are doing
at work we finish them in the frame
doesn't take that long
even old ps4/xboxone handles it fine (just)
pc smashes it though
Maybe I'll go that route first and if it becomes an issue run it outside of the update loop
one tip is though make sure you split your workers evenly not by chunk
if you have like 50 actors doing pathfinding
and they're all in the same chunk
and go on the same worker
but all your other workers are empty
bad things happen
Yeah I was already thinking that may be an issue, thanks!
the new ScheduleGranularity.Entity can help you with this
Is this on 0.50?
yes
.ScheduleParallel(this.QueryWrite, ScheduleGranularity.Entity, default, dependency);
/// <summary> /// Describes how the entities that match an `EntityQuery` should be distributed when a job is scheduled to /// run on multiple worker threads using `ScheduleParallel()`. In most cases, <see cref="ScheduleGranularity.Chunk"/> /// should be used. /// </summary> public enum ScheduleGranularity { /// <summary> /// Entities are distributed to worker threads at the granularity of entire chunks. This is generally the /// safest and highest-performance approach, and is the default mode unless otherwise specified. The /// entities within the chunk can be processed in a a cache-friendly manner, and job queue contention is /// minimized. /// </summary> Chunk = 0, /// <summary> /// Entities are distributed to worker threads individually. This increases scheduling overhead and /// eliminates the cache-friendly benefits of chunk-level processing. However, it can lead to better /// load-balancing in cases where the number of entities being processed is relatively low, and the cost of /// processing each entity is high, as it allows the entities within a chunk to be distributed evenly across /// available worker threads. /// </summary> Entity = 1, }
this specific case is exactly why this option exists
Ah cool
Still on 0.17 but that is an easy change once I switch
Something else I'm not 100% sure on how to do, is specific to the project I'm making. I basically have a very very large world, and the server will spawn entities for stuff like trees whenever an NPC or player is near. Now for pathfinding I need to know where those trees are so I can path around them. The question is how to store those things.
My current approach is just using a NativeMultiHashMap<int2, Entity> which stores all entities per chunk, but I have no idea how multi hashmaps behave once they get very full. The other option would be to use the CollisionWorld to overlap rectangle and find all entities on a chunk that way, but it might be too slow...
generally you'd write this info to your grid
i'm not exactly sure what/how your hashmap storage works with your pathfinding
The grid is too large to store it in memory
navmesh etc
It's procedural so I can't precompute it
oh thats much smaller than i was expecting
honestly i'd just store that in memory
thats only 128MB if each tile is 1 byte
if you only need a bool per tile (walkable, not walkable) and bit packed them that'd only be 16MB
unless you're targeting last gen consoles, xboxone, ps4 etc
I might need to stick to 1 byte per tile since I would like varying costs in the future, but 128mb still seems fine
i don't think any other platform is going to have memory issues on that
Switch would probably 
yeah i count that as last gen
How much RAM do ps4 and xbone have? iirc it wasn't much since I think it was unified with VRAM?
i just don't develop on it so forget to write it
ps4 you usually get around ~4.5GB
xbox usually around ~4.9GB
xbox is better as it dynamically shares its memory between gpu/cpu
ps4 you have to specify memory split so there's always waste
The game is entirely 2D and makes heavy reuse of sprite atlases, so I think I'm pretty good on memory budget. It's all simulation data
honestly if you're not releasing this year i wouldn't care about last gen consoles
game sales on them are dropping fast
the effort to develop is not worth it
(switch is a different story i guess in this case)
My current approach is just using a NativeMultiHashMap<int2, Entity> which stores all entities per chunk, but I have no idea how multi hashmaps behave once they get very full.
anyway to your original point
they're fine if that's the direction you want to go
i regularly use/test maps with 200k elements
It just felt like a very lazy approach
But you are right I could just fit it in a grid
do whatever is easiest and optimize it later if you need
Thanks a bunch for all the answers! Hard to find stuff for ECS ๐ฆ
Hope it will change once 1.0 comes out
Also just out of interest, do you think the collision world approach is too slow? iirc I remember reading somewhere that the spatial partitioning scheme used was very fast
Also just out of interest, do you think the collision world approach is too slow?
I'm not particularly sure what you're talking about here. the bvh in the physics world is pretty fast for all that it enables. it's definitely not the fastest partitioning if you are doing something very specific but for general purpose it does well.
Basically during pathfinding whenever a chunk is visited that wasn't visited during that search, use the collision world to find all entities on that chunk
Instead of using my own grid / native multi hashmap
oh if you just want to do a AABB over a giant area that's not that big a deal tbh
Yeah pretty much
i think you could get away with a few 1000 of them
I already use it for some stuff, e.g. finding all relevant entities to sync for a specific client
That's more than I expected, but I'm thinking it probably won't scale for every operation that needs to check "is there a thing here?"
So I guess I'll stick with the hashmap and switch to a grid if I need to trade memory for speed ๐
really just won't know till you try it
always hard to theorize how something will scale
Definitely
anyway i got to go source some dinner, back in a little
But the granularity thing is really cool. If you were to schedule very "fine grained", do you think something like false sharing could become an issue? or is it outweighed by the multi threading? since like you mentioned single dangling threads with heavy workloads could become an issue since there is now work stealing or something like that.
Enjoy ๐
This would force me to move an entity to another chunk if I want it to request a pathfinding operation correct? Since I remember hearing somewhere that moving entities is comparable expensive and it is better to keep the archetype the same and query a boolean to check if a certain operation should be done. That wouldn't work with the granularity though, since all NPCs would be in the archetype, not just those that want to find a path
Well not in 0.50 you can just use the overload and unity still split it for you
In 0.17 you should use ijobfor and just pass the entity array from the matching query
Yeah but if I have something like:
public struct PathfindingComponent : IComponentData
{
public int2 Start;
public int2 End;
public bool RecalculatePath;
}
The query cannot check if recalculate path is true
So either I need to run it over all entities in the archetype or run a previous job to find all matching entities
Yep
I feel like enable bits would still really help with something like this
Isn't that also not a good idea at large scale though?
Either an Entity event with a single component entity field or something like an event system
What I'd do depends on my entity count
If you were only in dozens to hundreds per frame entity event is fine
I think you'll hit pathfinding limits before you hit event limits
That's something I'm still struggling with when it comes to ECS. Say I want to deal damage. My preferred way of doing it would be to create entities that have the target entity, damage type, damage amount etc. But the "better" way always seems to be either a simple component with a damage value that is 0 unless damage is being dealt, or a dynamic buffer with damage instances. Is that still valid even in 0.50?
(at large scale anyways)
Damage has been a heavy topic of discussion recently
Does the dependency management system take IJobFor into consideration? So if I pass a ComponentDataFromEntity with write permissions to it, will it schedule it properly and alert me of any race conditions?
Thanks, I wasn't sure.
calling
this.GetComponentDataFromEntity<>()
on the system
adds the dependency to the system
Ah yeah, that makes sense
only mistake you can make is calling EntityManager.GetComponentDataFromEntity<>()
which will break safety as the system won't get the dependency
(there are some very obscure reasons you might want to do this though)
Yeah I rarely if ever use EntityManager unless it's Run or WithStructuralChange stuff
any new tricks for particles in entities? are we still supposed to use gameobjects for it?
it should automatically create a companion-link entity iirc.
ah right, well that can turn out helpful. although I'm still not sure how to deal with instantiating. Previously I just used pools of particle systems so I'm still not convinced which benefit particle systems in Entities could give when I'm stuck with unbursted code anyway. I'm inclined to just leave this part in MB world and pull the necessary data from the ECS world.
i'm finding this very satisfying
think i'm about done with this save library, wonder what i should do with it now
maybe actually integrate it in my project to prove it works outside of demo scenes
How can I schedule a IJobParallelForDefer with a NativeArray?
IJobParallelForDefer is for lists in defer mode
what do you mean by with a native array
Specifically from EntityQuery.ToComponentDataArrayAsync
IJobFor.ScheduleParallel only takes an integer for scheduling though?
But won't the length only be valid once the async job completes?
no
native arrays cant change in size
the array you get back from that will be the right length
it might just have garbage data

Physicalized cubes are always satisfying
What's the use-case for the saving library?
does your game need saving?
It does indeed
bam use case detected
Do you have a link? That would be interesting to read
well the tldr is any change you make to any component will break all your users saves
or if you update entities
or if you change a namespace
or anything
That does seem like an issue
it has no capability of migration or making changes once you release your game
so don't ever intend to update your game if you use it
Just ship the game finished and without bugs duh
(it's not designed for saving, it's designed for building subscenes)
anyway that was my post about my experiences and why i'm writing this library now
this is the 3rd time i've written a save library*
Do you know if Unity is planning on adding something actually usable? Like you showed me earlier, there are quite a few games that may want to actually save the player's progress
first 2 at work, this one for my personal use
first one was using serializeworld (ok so i didn't actually write most of this it was mostly another dev, i just used it a lot and experienced it's weaknesses)
second one is doing well and has been shipped and is out in the world
and we've done multiple data migrations via patches
In the second approach, are you using reflection or is it just manually "rigging" the loaded data to the newly created entities?
no reflection at all in that approach
second approach being what i did at work from memory
These archetype containers are just serialized normally? With some kind of serialization library (JSON or whatever) not using the Unity serialization system right? (Since that would break if Unity changes something)
in second case?
Yeah
they're unamanged, we just write the bytes directly to a stream
Wouldn't that still break if you add something to an archetype container?
yeah but you can migrate it
Just making sure I understood it correctly
each container is versioned
on loading save it runs a routine after deserializing into the container to see if its latest
if not migrate to next version
repeat until it's latest
simple function that just converts 1 container to next
Cool
we try to save as little as possible
So similar to standard DB migration stuff
so that we have to write as few migrations as possible
less you save, less issues you have
I'm amazed the first approach even worked for you, migrating the serialized world sounds like a nightmare
yeah we have this dev that loves this low level type of stuff to a detriment ๐
it worked greatish but would just keep randomly breaking
is your library on github?
not atm
mostly nested structs turned out to be a nightmare
and any change on any component required a migration
so we were just constantly writing them
every day of work was like, write a migration ๐
too much data saved
the other issue is, can't make changes
if design change your creatures max health from say 100 to 150 for balance
that change won't be reflected in any save file until they start a new game
instead if you just dont save max health then when they load a save, it'll populate the new value instead
so yeah one flaw with approach 2 is if you make a change to a component
I'm currently using SerializeWorld, so I'll need to change to something else once saving is actually needed.
I also ran into the issue that "too much" data was saved, especially stuff like SystemStateComponents that shouldn't exist on a load. I also used attributes to strip them in a copied world before the world is serialized.
and that component exists in multiple save containers
you have to migrate multiple containers
thankfully this doesn't happen much
but still something i wanted to avoid
also there is a lot of boilerplate setting up containers per type
and its a bit against a data driven design where design cant just add something new to an archetype
without a dev coming in and adding it to the save
i.e. lets say making a building grow fruit like a plant
really happy with how this has turned out now
Yeah definitely looks promising, good stuff
just need to get some production testing on it
but thats unlikely to happen anytime soon, so at least get some real world testing on it
Have you seen the GDC 2022 Entities videos? I saw they have a new api instead of Entities.ForEach and it's just Lamba with Query(...) now
Also a Baker api instead of IConvertGameObjectToEntity
@rotund token how did you handle entity reference (de)serialization in the second version of your saving system? Just save a GUID or something instead of the entity index / version?
And keep track of Guid->Entity when deserializing?
we just save the entity reference
on deserialization the first step is to create all the entities which are then mapped to their previous reference in a hashmap
then during the apply step the fields are remapped
in my current (3rd) approach this is automatic now using TypeInfo
Thanks!
var prefab = GetPrefab(container.Type);
var newEntity = EntityManager.Instantiate(prefab);
remapHashMap.Add(container.Entity, newEntity);```
the basic theory (obviously a bit more advanced for the sake of performance, does the whole thing in batches)
was there something other than?
DOTS authoring and debugging workflows in the Unity Editor
yeah, that's the video, the other one linked here (https://forum.unity.com/threads/dots-talks-gdc-2022.1271147/) wasn't that interesting
ah yeah watched those both a few weeks ago
If I need to store a reference to a native array inside an IComponentData, how would I do that? Can I cast UnsafeList to the NativeArray type?
Since there is no UnsafeNativeArray
i would first ask, why
Just easier access to a native array that lives somewhere else
again why
Would you store it in a dynamic buffer?
(why not use dynamic buffer [unless your enzi and you need to simulation 4bazillion things at once])
Is storing a lot of data in dynamic buffers fine? It's a 1024*512 array
is this your world grid?
we store our world grid in a buffer
Basically references to procedural world cells for faster lookup
It's a native array of entities
it depends how many systems need it
i don't like sharing containers between systems
(i have a strict, avoid all system coupling rule)
How big is the grid?
ours is only 2k x 2k but we store quite a bit of data per cell
i think its like 30 bytes per cell
Still bigger than what I have, but I could potentially have multiple worlds
for the record if i was around when this grid was actually created
(and i was as experienced as i am now)
i would not use a grid ๐
i'd make us use a navmesh
Like I said it's actually not the data, just a lookup from world chunk -> world cell entity
yeah anyway
They're voronoi cells. Ideally I would use a better lookup scheme
if you only need it in 1 system then i'd use a native container stored in that system
if i needed it in multiple systems for some reason i'd use a buffer
for the record, i do have a dynamichashmap i released publicly
So like dynamic buffer but a hashmap?
yeah
under the hood its a dynamic buffer
it just uses the dynamic buffer as a hashmap (or multihashmap)
its pretty neat
Cool, that could come in handy, do you have a link?
Awesome, thanks.
i might just update it
i forgot the set method on the hashmap
when i first uploaded this
note it won't compile out of the box as you need to give yourself a bit of internal access from memory
i intend to release my core library (this included) at some point
I honestly don't get using DynamicBuffers. Why store something in a subpar system that is not built for it. Either store the data in NativeContainers or in IComponentData. I don't think there's any case where you'd HAVE to use a DynamicBuffer
Easier access in jobs?
All NativeContainers are easy to access.
How do you store the reference to the container in the component?
The only argument that I read today, from DreamingImLatios is faster building of the data. I let that slide. But with more or less static data this benefit gets marginal
If I don't misunderstand, with a pointer?
I guess people coming to ECS just aren't very used to working with pointers in C#
let's see
- unable to update data at runtime in entities 1.0
- unable to use entity journaling
- coupling systems together
- the performance is actually fine unless you are doing ridiculous work loads
- saving is a pain in the ass
I think I do misunderstand ๐ Well, this whole binding of very global data to entities is odd in most cases. I would never store a grid as entities for example
I store the voronoi cells as entities because it just makes iterating over them very easy, but it could obviously be done in a different way
NHM and NMHM is great for any grid data
I mean, I get why people use them. I also did because the iteration and creation is so easy. I just hope you don't get stuck with a solution that you want to change at a later point.
And were it not for the performance overhead I'd use them too ๐ hehe
I think it's part of the learning process, and I am at that point for a lot of things.
I still have issues when I need to map "complex" things like e.g. large multihashmaps on a per entity basis.
For example, I have an entity that represents a world, and each world has a NativeMultiHashmap<int2, Entity> that contains the chunk->entity mapping as a simple spatial mapping. I'm still not sure what the best way to access this data in a job is. I'm guessing just a pointer is easier in a lot of cases
correct, the entity is just a placeholder to poll the data you need, right?
Yes, and other entities have components that reference what "world" they are in
get comfortable with pointers, it'll be worth it in the long run. Also, jobs that build mappings with pointers are blazing fast
Basically like a simple reference, and the world entity should contain (but doesn't) the collection
I do this to have faster access for LocalToWorld matrices.
Any tips? I've barely worked with pointers in C#. How do they interact with generic types? Since I can't seem to get a pointer to one
Do I just use any value and make sure to properly cast it back to the real value?
(like an IntPtr or something)
what on earth does this error mean? The type 'Unity.Collections.NativeArray<CellNavigationSystem.Node>' cannot be used as type parameter 'T' in the generic type or method 'FixedStringMethods.IndexOf<T, T2>(ref T, in T2)'. There is no boxing conversion from 'Unity.Collections.NativeArray<CellNavigationSystem.Node>' to 'Unity.Collections.INativeList<byte>'. [Assembly-CSharp]csharp(CS0315)
From this code:
public struct Node
{
public FixedString64Bytes cellWorld;
public float3 coords;
//TODO: Whitelisted covens / keys
}
static NativeArray<Node> nodes; //Nodes in graph
Node start;
openList.Add(nodes.IndexOf(start)); //error here
@rotund token all correct. It's not that DynamicBuffers don't have their place if you know what you're doing and keep it simple and a small memory footprint. But right now it's a pitfall for newcomers when they think of, I need an array, oh I use DynamicBuffers. Memory layout goes to shit, job overhead increases and soon lots of ms are gone just for DBs access. And as I said, a million times now. I don't understand why DBs are slow. They should be faster than NHM in theory.
that's true, that's why I try and avoid using them unless I really have to
UnsafeUtility has most of the useful methods. For example AddressOf<T>
Ah such just a void*
yep, they just use void pointers. Can be cast to anything you like
Why does AddressOf get the ref value? To avoid copying?
pointer access inside IJobEntityBatch
correct
in fact, lots of performance can be gained if you avoid "by value" and use get the reference
CDFE is one contender for screwing up performance because you get local struct copies
Is this for faster access compared to chunk.GetNativeArray?
Yeah I'm still not 100% sure when copying can be better than using in inside a job when passing stuff to functions
yes, because you can get a ref
see you keep claiming this performance thing, but i don't see any evidence. i can write a performance test in a few minutes and every time i do nativearray and dynamic buffer perform exactly the same
in fact i just did
numbers represent length buffer/array, number of entities with buffer/array
so the
bottom test is 10,000 entities with 1000 length buffers
top is 100 entities with 100,000 length buffers
it's fraction of a millisecond difference over 10,000,000 elements
can you share the test? I can start mine again to compare
yeah of course
well that's good to know
the only thing i can think of is you are measuring with safety on
this is what happens when full safety is on
but the memory fragmentation can still be a problem
its 2 orders of magnitude different
like storing lots of buffers on lots of different entities will always be slowly than one single list
this native array test is storing it on the component so has no safety even when safety is on
{
[NativeDisableUnsafePtrRestriction]
public void* Ptr;
public int Length;
}```
nah, safety is off and numbers weren't that horrible ๐
it's probably this different because safety breaks the simd in this test
pick it apart
make sure i haven't done something stupid
test is just summing every number in the buffer/array and writing it to an index
I'll start up my old performance project. I've done very specific tests for reading mostly. But also some write tests
i would love to see a test where the performance is significantly different
a test script that is still in my project, DB still lags behind NMHM
because from the source code i dont really understand why this would ever be the case
the trick is you obviously have to always use AsNativeArray when reading/writing
otherwise apart from having to do a condition check every time, it'll never optimize
which a lot of users won't know by default
That's good to know ๐
because it has to do this
UnsafeUtility.ReadArrayElement<T>(BufferHeader.GetElementPointer(m_Buffer), index);
UnsafeUtility.WriteArrayElement<T>(BufferHeader.GetElementPointer(m_Buffer), index, value);
this bit here
BufferHeader.GetElementPointer(m_Buffer)
{
if (header->Pointer != null)
return header->Pointer;
return (byte*)(header + 1);
}```
so if you write/read direct to it, it has to check this every call
which breaks simd
and just adds a huge amount of overhead
this is because it needs to check if the buffer exists in or outside the chunk
honestly, my preference would be that unity made buffers just always live outside the chunk
and use a FixedContainer or fixed[] if you want a chunk of memory to live in the chunk
I was was already using AsNativeArray. Joachim pointed that out to me but it honestly didn't make any difference. Didn't know about the optimization in depth that can be done with it so it's odd I couldn't see any improvement
I've pasted this back then. Still the same read test
My buffer is a LOT bigger ๐
im confused what you're comparing here
the lookup performance of a 250,000 buffer vs a hashmap?
oh wait no i see
I just reduced the data to one integer and now DBs are a tid bit faster than NMHM
wait im so confused
I got slower results in any case with InternalCapacity(0)
in essence this does the same thing. iterate over all entities and check if a value is 34
one time data is in DBs, the other it's in a NMHM
I allocate 5 elements for every entity, correct. So 250.000 * 5
haha, "fascinating" - well this all started when I used a DynamicBuffer for cooldowns and calling Clear in a job took like 1.5ms for 250k
then it went down the rabbit hole
they match pretty much. you have a faster cpu! ๐
yeah i splashed out for a 3900X a couple of years ago
paired with my awesome 1060gtx ๐
on my ultrawide
is not a great gaming experience
but a great dev experience!
nice! I'm still on an i7-4770k
sooo many worker threads drool
lol sick
so having a looked at the bursted code
what i think the different is
isn't the actual nmhm vs hash
it's the overhead of the job
that's what I've been saying! ๐
have you
all im trying to compare is apples
a native array on a component
vs a buffer
and you're like 7 levels deeper
what you seem to be comparing is
IJobFor vs IJobEntityBatch (or chunk in this case)
if you were storing a world grid on a single entity
its not going to be any different
but if you're saying the lookup time of 250k entities is a lot more expensive than iterating a native container
then i'm onboard
Can you rephrase that? In my own words, the chunk iteration of DBs should be faster than a NMHM.
entity iteration of chunks
is slow as entity count goes up
it has to loop through every chunk (even empty ones) and check the archetype vs the query
if you have a lot of archetype changes leaving empty chunks everywhere
oh really, I wasn't aware of that
you get quite a noticeable performance degeneration over time in a game
we have this issue a bit on old gen consoles
game might start at a solid 30fps, an hour later down to 24
I thought this was a one time thing per frame. huh
this is one of the reasons i've been moving to large jobs instead of lots of small ones
i believe 0.50 did a bunch of optimzations here though i havent benchmarked yet
Hm, I've been doing the same thing although my archetypes are stable. No structural changes
i think it does a lot more caching for this
especially if you aren't changing archetypes
so it might be less of an issue than it used to be
so take what im saying with a grain of sand
(about slow down over time)
i haven't benchmarked our console builds since we upgraded
a downside is also that you get struct copies when reading DBs
you dont have to
in my old test I also get struct copies from the NMHM so that's fair but that can be optimized
it has a
ref ElementAt(index)
what's missing is a nativearray version of this
{
public static ref T ElementAt<T>(this NativeArray<T> array, int index)
where T : struct
{
return ref UnsafeUtility.ArrayElementAsRef<T>(array.GetUnsafePtr(), index);
}
which of course i've added ^_^'
but yeah that should be built in
every other container has it now
db, nativelist etc
right, let's see how it performs then
Is it generally a bad idea to instantiate prefab entities at runtime? I've heard about a push towards converting all entities at the beginning and avoiding creating new ones, even through prefab instantiation
i fall on the side of it being bad
more for the future and 1.0 features which won't work from runtime created archetypes
but i also just think as a general principle in a larger team
things should be editable without code
Ok, so for entities that should only "exist" for short periods of time, do I toggle them through the Disabled component, instead of instantiating and destroying them on the fly?
what type of entity are we talking
It's an entity that has a component ProximityComponent that is queried by ProximitySystem, but some of those entities will only exist for short periods of time
(i should say there are definitely cases where entities can and should be created via code such as state entities etc just game entities shouldn't be)
yeah so that case seems fine to me
i think if it more like code
what should be public, what should be private
Could you elaborate on state vs game entities? It sounds like a useful litmus test
if the entity should be changable by designers such as creatures, buildings, etc it should be Public therefore setup via conversion
ElementAt makes zero difference in this test. That's so weird ...
if an entity is for states used by developers only, etc it should be private and (usually) created on the spot
yeah because that's not the performance issue on this test you aren't really iterating much
there are definitely cases where more developer based config should still be converted
like say, server settings etc
uh oh, the temporary ProximityComponents also has configurable TimeToLiveComponent values. IT's definitely a game/public entity. On the surface it's used for temporary proximity objects like incoming damage markers (a giant boulder is coming towards you)
Thanks for the tip
Though I'd argue that you can configure the instantiated entity if it's a prefab by editing the prefab's values
so i guess you could break it into 3
public - designers/artists - conversion
internal - configuration - conversion but hidden away
private - application state without configuration - create in system
this is how i do it anyway
internal/private is more my preference than a hard rule i'd say
but at the very least i think anything designers/artists want to touch should be conversion
Thanks. I tend to see in black and white. I'll stick with instantiating prefabs since I'm just a one-man team and wrangling with Disabled is causing headaches
personally all my settings exist on scriptable objects which map to a component during conversion
this way i can actually make changes to my settings/scriptable object, reimport subscene
and update the values at runtime (with writeback)
tertle, do you have a suggestion for handling particles?
nah not really i'm just putting this off
btw if you want to make your test faster
public struct NHMElement : IBufferElementData```
take the buffer out of the chunk
so you can get more entities into it
Guess me too for the 3rd time but I'm building a half-assed thing now ๐
vs [InternalBufferCapacity(8)]
huh, it was slower in my case with 0 cap. also my vs2022 can't decompile anymore, so no more integrated entities source code QQ
small archetypes are more important than pretty much anything when it comes to a lot of entities
cache usage be damned
yep, I need to redo a test where the chunk size is larger than 16k
anyway that improvement makes me feel a bit better about my poor db
i really wnat to find a way to inject [InternalBufferCapacity(0)] onto LinkedEntityGroup
there is no reason that ever needs to live in the chunk =\
pretty sure i'm just going to be stuck editing that package at some point
but back onto new topic
i've been putting off figuring out my hybrid workflow
i kind of liked how my cinemachine wrapper worked though
so might just do something like that
not sure if it'll scale though
wish viual effect hurried up with a gameobject free implementation
yep, just read today that it's postponed till 2022 with no more info
For what reason?
it does even with cap 0?
144 of that is a linked entity list ๐
ah i see
yeah that's really problematic. Editor also injects lots of overhead comps
So you only know the final chunk capacity at runtime. or you calculate it
well it is 2022 already! do you mean unity 2022?
haven't checked if the new archetype window is more capable now