#archived-dots

1 messages Β· Page 266 of 1

rotund token
#

well to be fair, they didn't expect anyone to need to do this

#

they exposed a very easy to implement editor for fields

#

who's crazy enough to hijack a block of memory via dynamic buffers to use for other things

viral sonnet
#

yeah! you are not supposed to do all those crazy things!

#

I'm kind of disappointed that they didn't. having hashmaps on entities is a very general use case I feel like

#

sure, to abuse the DB is kind of unexpected ^^

#

but then again, it's really not

#
                {
                    UnityEngine.Debug.Log("buffer catched");
                    Property = CreateInstance(typeof(DynamicBufferProperty<>));
                }``` kind of unexpected that the instance figures out the correct TElement for the DynamicBufferProperty
#

i need to get the types of TKey and TValue too. good thing that is possible with type.GetInterfaces() and then GetGenericArguments()

#

Ok, that thing annoys me way too much right now. There's another abstraction I've found that registers the type. I'm pretty sure it's easier to write a custom inspector for this outside of the dots debugger. This all makes little sense when having to edit the core package anyway.

gusty comet
#

stupid question, but since entities and other libraries related to dots are all in preview mode... is it more safe to build a game normally instead ?

gleaming surge
#

Yea Unity officially does not recommend users to go into production using preview packages

white island
#

Okay, so I am about to attempt to create a value type-only AStar implementation based on CodeMonkey's tutorial, but adapted to fit a node graph rather than a grid. I'm not 100% sure what I am doing, so could someone look at this and tell me if I'm at least on the right track?
https://pastebin.com/faqhvxCF

#

also: How would I go about working with Node Data deserialized from disk? I'm thinking somehow put it into a default list kept in persistent memory, and then during the job I clone it into a new list and mutate the data inside?

viral sonnet
white island
#

I'

#

oops

#

I'm thinkling about jobs because this is for a multithreaded task

viral sonnet
#

astar can be translated pretty much 1:1 into burst

white island
#

What about deserialization

viral sonnet
#

multithreading 1 astar request itself wouldn't make much sense

white island
#

it isnt 1 request

#

a lot of entities will pathfind through this static node graph

viral sonnet
#

node data can live in a hashmap or one dimensional grid array

#

ok, I just say this because you have set the PathFindJob up for only 1 request

white island
#

What. where

viral sonnet
#
        public struct PathFindJob : IJob
        {
            public float3 start;
            public float3 end;```
#

This could be arrays and an IJobFor. In general, use IJobFor because that's newer and more flexible (can be single or multithreaded)

white island
#

what does this error mean?
Cannot modify the return value of 'NativeArray<Node>.this[int]' because it is not a variable
Context:

public struct Node
    {
        public float3 position;
        public int index;
        public int lastNodeInPath;
        //TODO: World

        public int g; //distance from start point
        public int h; //distance to end point
        public int f;

        public void updateF()
        {
            f = g + h;
        }
    }
//...
NativeArray<Node> nodeGraph = new NativeArray<Node>();
//...
int workingNode = getLeastF(nodeGraph, openList); //get least F in open list
NativeList<int> validConnections = getValidConnections(connectionsSource, workingNode); //find all open connections
//Set parent nodes
foreach (int item in validConnections)
{
           nodeGraph[item].lastNodeInPath = workingNode;
}
viral sonnet
#

you can't directly change it. var tmp = nodeGraph[item]; tmp.lastNodeInPath = workingNode; nodeGraph[item] = tmp

white island
#

right. I forgot about taking out and setting in stuff

white island
#

Alrighty, I've made a first draft of the AStar implementation. I haven't tested it because I would need to create a graph somehow and I have to make dinner. Can someone please do a once-over to make sure I'm not doing anything stupid/will crash my computer?
https://pastebin.com/Y5UyLwmg

viral sonnet
#

Going to bed now. Haven't found any obvious errors although getClosestNode will be a performance hog. either hash the position to a proper index or use a hashmap. for testing it's okay to leave it like that.

white island
#

I don’t know how I would do that but okay

#

This is a problem a K tree or whatever it’s called could solve but I don’t know how to make those yet

#

K-d tree I mean

muted field
#

if you're comfortable with unsafe stuff it can be quite handy since you avoid all the copying of struct data

rustic rain
#

is it ok for renderer to do huge spike once in a while?

rotund token
#

ideally no

#

spikes are much more noticeable to a player than low fps

#

e.g. i'd rather solid 60fps than 90fps with spikes

rustic rain
#

IJobEntityBatch does it support parallel scheduling?

rotund token
#

yes

#

everything code gens into IJobEntityBatch

#

entities foreach, IJjobEntity are just IJobEntityBatch under the hood

rustic rain
#

no I mean

#

I do use IJobEntityBatch rn

#

already

#

since I need shared comp index

#

I did it all as Schedule

#

now I want to transform into Parallel

rotund token
#

ScheduleParallel? πŸ˜„

#

or are you wanting to run multiple jobs in parallel?

rustic rain
#

no, I want to make my job run in parallel through chunks

rotund token
#

either i'm confused or that is exactly what ScheduleParallel does

rustic rain
#

well yeah

#

but where do I get index

#

for parallelWriter

rotund token
#

you can just use chunkindex

rustic rain
#

oh

#

it's that easy?

#

huh

rotund token
#

batchindex*

#

yes

#

if you happen to need entityInQueryIndex there's also a IJobEntityBatchWithIndex for that

#

left out by default implementation for performance

rustic rain
#

yeah, I read about it in manual kek

#

not sure why I'd need it though

rotund token
#

sometimes you want to write a result to a native array

#

1 element per entity

rustic rain
#

Does NativeMultiHashMap supports parallel writing?

rotund token
#

when you do AsParallelWriter it does

rustic rain
#

hm

#

I see

#

I assume

#

reading doesn't require it?

rotund token
#

no

rustic rain
#

all right, it's all parallel now
But I can't tell if perfomance good or bad, hm

#

1651 entities

#

AI agents

#

with choice between 2 tasks: hug player, run around (if no players, or if player is too close)

#

each task schedules at least 3 jobs per update

#

also each of them has LineRenderer xD

#

all right, I think it's good

#

almost all perfomance is on ManagedComponents

#

which only required for presentation

#

meaning if they are outside of player system

#

they can be disabled

rotund token
#

looks cool

rustic rain
#

How to find entity in hierarchy through it's id+version?

rotund token
#

that should work

#

as long as you haven't named your entities

rustic rain
#

oh

#

I did name them kek

#

Is there any way to reference Editor in Runtime?

            #if UNITY_EDITOR
            World.GetExistingSystem<SelectionTool>();
            #endif
#

I don't want to force PC render selection texture twice

#

kinda just want to pass Entity to it

#

maybe reflection?

rotund token
#

dont know what you're asking

rustic rain
#

SelectionTOol is system from Editor folder

#

I want to do Editor code

#

to select entity

#

in Runtime system

rotund token
#

make selection tool a runtime tool then?

rustic rain
#

it is runtime tool

#

but I can't insert code for editor

rotund token
#

SelectionTOol is system from Editor folder
then why is it in editor folder

rustic rain
#

well, it's empty basically

#

if I simply do code for EntitySelection in editor

#

same thing

#

ah nvm, looks like I needed to reference editor in assembly def

#

Hmm, when I disable System, does it run OnStopRunning?

#

yes, it does

rustic rain
#

meanwhile Rider can't even find anything

rotund token
#

are you trying to use entityInQueryIndex in IJobEntity?

rustic rain
#

yeah, and I use attribute

#
            private void Execute(Entity e, [EntityInQueryIndex] int entityInQueryIndex, ref MoveTarget moveTarget,
                in FollowShip ship)
            {

It didn't cause trouble before

#

that's what is curious

rotund token
#

i'd just give unity a restart then reimport the the assembly if it's still an issue

#

those c# compiler version errors usually just need a restart

rustic rain
#

I tried it all

#

even full reimport

#

kek

#

Microsoft (R) Visual C# Compiler version 3.8.0-dev.20527.1 (53dc6556)
Copyright (C) Microsoft Corporation. All rights reserved.

error SGJE0003: The parameter 'entityInQueryIndex' of type int will be ignored.
Assets\Runtime\HybridGO\CompanionLinkDisableSystem.cs(15,28): info SP0001: Diagnostic 'CS0282: There is no defined ordering between fields in multiple declarations of partial struct 'CompanionLinkDisableSystem.SetModeJob'. To specify an ordering, all instance fields must be in the same declaration.' was programmatically suppressed by a DiagnosticSuppressor with suppression ID 'SPDC0282' and justification 'Some DOTS types utilize codegen requiring the type to be partial.'
Assets\Runtime\TestAI\FollowShip.cs(23,32): info SP0001: Diagnostic 'CS0282: There is no defined ordering between fields in multiple declarations of partial struct 'FollowShipSystem.FollowShipJob'. To specify an ordering, all instance fields must be in the same declaration.' was programmatically suppressed by a DiagnosticSuppressor with suppression ID 'SPDC0282' and justification 'Some DOTS types utilize codegen requiring the type to be partial.'
Assets\Runtime\SpaceshipMovement\MovementLineSystem.cs(83,32): info SP0001: Diagnostic 'CS0282: There is no defined ordering between fields in multiple declarations of partial struct 'MovementLineSystem.SetLineRendererJob'. To specify an ordering, all instance fields must be in the same declaration.' was programmatically suppressed by a DiagnosticSuppressor with suppression ID 'SPDC0282' and justification 'Some DOTS types utilize codegen requiring the type to be partial.'

rotund token
#

reorder it

#

so the entityInQueryIndex comes first

#

πŸ€·β€β™‚οΈ

rustic rain
#

[EntityInQueryIndex]
Is that attribute only required in IJobEntity?

#

nothing else?

#

yay

#

I found it

#

I did miss one attribute on sketchy job

misty wedge
#

@rotund token How do you store your graph nodes?

I'd like to use graphs for a few systems, and ideally store their data in blobs, but I have no idea how to store it, since in normal OOP I would just use inheritance

rotund token
#

i use inheritance πŸ˜„

#

just my own unmanaged version of it

#

(not too dissimilar to how physics stores their colliders for example)

misty wedge
#

So from looking at it on a surface level, the collider is a struct that stores it's own "true" type in an enum, and the interface methods are implemented by casting the collider base struct to a pointer of the relevant type and calling the actual method (thus getting the proper memory offset for the correct type)?

#

e.g. Collider.MemorySize

#

How / where are you storing the struct though? Since you can't store a Collider struct, as the size would be incorrect when compared to the "true" size.

#

Basically, If the graph lives on a scriptable object, it's easy enough to just use an interface type while working with nodes, but how are they stored in the final blob?

rotund token
#

im not using blobs but my own memory storage

#

(blobs had some restrictions in 0.17 that made it painful but it should be quite doable in 0.50 hopefully again)

misty wedge
#

How would you store them though? What is the type used for e.g. a BlobArray? Just use a struct type that is as large as the maximum subtype?

#

Or just use a byte array and somehow reinterpret the nodes from that?

#

Guess I'll take a look at the unity physics implementation. Thanks for the tips!

rotund token
#

i learn a lot from that

#

but yeah its just stored as a pointer in a blob asset reference

#

the blob asset reference is a collider type

#

but its allocated as a larger size

misty wedge
#

Yeah, I just saw that. The BlobAssetReference<T> Create(void* ptr, int length) method seems useful for storing generic data, and it's nice that the actual reference can "remember" any arbitrary type with the generic parameter

#

How do I convert the blob asset reference to the true type? Is UnsafeUtility.AsRef<MyTrueType>(blobAssetReference.GetUnsafePtr()) the best way? Since that seems to be what .Value does

rotund token
#

probably my last update on this rollback side project
https://youtu.be/LST1DBzHImY
took Unity's boids demo, cranked the amount from 50k to 250k and added rollback

#

Any spikes are basically my poor gpu unable to keep up 😐

misty wedge
#

Very cool. It also looks incredibly satisfying with them just starting out as spheres and then beginning to move πŸ‘Œ

#

How large is each frame snapshot for 250k entities?

rotund token
#

i haven't checked but it'd be large especially since i turned off compression for this quick demo

#

18MB uncompressed

rustic rain
#

hmmm

#

Feels bad IJobEntity is not IJob

#

I kind of want to use some general Interface

#

for my jobs

#

I'm trying to reuse some code, but since it's all struct/interface based, it's impossible sadkek

rotund token
#

They're completely different things

rustic rain
#

I know

#

it's just that I am going to have exactly this same thing in every my System

#

and I need to do Schedule

#

for them

#

and since jobs are interfaces

#

I can't really do it

#

and have to rewrite same thing

rotund token
#

Make your system generic, use IJobEntityBatch?

solemn hollow
#

@rotund token Do ChangeFilters now always cause syncpoints or are there conditions? do you know where i can find it in the code?

rotund token
#

Basically all filters will cause a sync point atm

#

Its not a hard sync point on everything

#

Just on any component the job depends on (maybe only the filter)

rustic rain
#

using batch is a waste

#
[BurstCompile]
        private partial struct SetWeightJob : IJobEntity
        {
            void Execute(ref DynamicBuffer<ActionWeight> actions)
            {
                actions.Add(new ActionWeight() {type = ComponentType.ReadOnly<DoWorkTag>(), weight = 0f});
            }
        }

        [BurstCompile]
        private partial struct ClearTaskJob : IJobEntity
        {
            public EntityCommandBuffer.ParallelWriter buffer;

            void Execute([EntityInQueryIndex] int entityInQueryIndex, Entity e)
            {
                buffer.RemoveComponent<DoWorkTag>(entityInQueryIndex, e);
                buffer.RemoveComponent<HasJob>(entityInQueryIndex, e);
            }
        }

        private SetWeightJob _setWeightJob;
        private EntityQuery _setWeightQuery;

        private ClearTaskJob _clearTaskJob;
        private EntityQuery _clearTaskQuery;

Here one example

rotund token
#

What do you mean by waste? Like effort?

solemn hollow
rustic rain
#

when working with classes you reuse a lot of code

#

with jobs it seems

#

impossible

rotund token
#

Well that's what I'm saying use the same job in every system

rustic rain
#

but it's fine

rustic rain
#

unless maybe if it's generic

#

but even in this case

#

it can't always be same

rotund token
#

You can totally reuse same job and pass custom logic to each with generics

rustic rain
#

I know, but the point

#

eh, I'm just complaining

#

Still, requires too much abstract templates

#

kek

#

I already have like 5

#

and I only have 2 total actions

#

lol

#

btw, is that a thing for Rider to move "errors window" the same way as in VS?

#

so it shows all error/hints/warnings as full messages?

pliant pike
#

that brings me up to a question what are the ways people are using to reuse code in jobs in general?

#

like I was using a struct before but I have to remember to create a declare it before the job every time or a static method I guess thats a bit better

worthy rampart
rustic rain
#

yeah, a lot of in ecs are simple math after all

pliant pike
#

ok cool, thats pretty much what I'm doing

rustic rain
#

Is there a perfomance difference between ForEach and IJobEntity?

#

if code is literalyl same

solemn hollow
#

there shouldnt be because both are running through codegen and end up beeing an IJobEntityBatch

weary dagger
#

How can I change the ECS script templates?

misty wedge
weary dagger
#

Thanks, the package manager resets the change but I can create my own script templates menuItem to show my templates.

misty wedge
#

The package manager resets it because you haven't made a local copy

#

I would recommend you just make your own templates though

weary dagger
#

It will be a mess to share the project if I have a local copy, having my own templates will be better, again thanks for the information.

rustic rain
#

ahem, these errors are so missleading

#
        protected override void OnStopRunning()
        {
            if (!Enabled)
            {
                _clearTaskJob.buffer = bufferSystem.CreateCommandBuffer().AsParallelWriter();
                Dependency = _clearTaskJob.ScheduleParallel(_clearTaskQuery, Dependency);
                bufferSystem.AddJobHandleForProducer(Dependency);
            }
        }
#

I simply added this

#

meanwhile I run this same job, in OnUpdate

#

meanwhile this job literally has nothing to do with LTWs

#

kek

misty wedge
#

It's not completing the dependencies it requires

rustic rain
#
        private partial struct ClearTaskJob : IJobEntity
        {
            public EntityCommandBuffer.ParallelWriter buffer;

            void Execute([EntityInQueryIndex] int entityInQueryIndex, Entity e)
            {
                buffer.RemoveComponent<DoWorkTag>(entityInQueryIndex, e);
                buffer.RemoveComponent<HasJob>(entityInQueryIndex, e);
            }
        }

There it is, kek

misty wedge
#

OnStopRunning does not force the system to complete required dependencies, that's probably why it is complaining

rustic rain
#

oh

#

all right, I guess I should Run it instead

pine plaza
#

hey does anyone know how to use "dont destroy onload" in dots ?

rustic rain
#

wdym?

#

for what?

pine plaza
#

for my networking game

#

=)) i use netcode package so there is entity and it 's be destroyed when change scene

solemn hollow
#

@pine plaza you should probably have an extra subscene where you have all the stuff you dont want to unload

rustic rain
#

hmm

#

what would be better solution

#

run a job that literally just removes component through buffer

#

or simply move some new system to sync point

#

and remove all components through entity manager?

solemn hollow
#

if you can batch the command the second option is better

rustic rain
#

wdym batch the command?

solemn hollow
#

when you can use the entityquery to remove the components

rustic rain
#
        [UpdateInGroup(typeof(EndSyncPointTickSimulationGroup))]
        private partial class ClearSystem : SystemBase
        {
            protected override void OnCreate()
            {
                _query = GetEntityQuery(typeof(HasJob), typeof(DoWorkTag), ComponentType.Exclude<MoveTarget>());
                _types = new ComponentTypes(typeof(HasJob), typeof(DoWorkTag));
            }

            private EntityQuery _query;
            private ComponentTypes _types;
            protected override void OnUpdate() { EntityManager.RemoveComponent(_query, _types); }
        }

        protected override void UpdateWithBuffer(EntityCommandBuffer.ParallelWriter buffer)
        {
            Dependency = _setWeightJob.ScheduleParallel(_setWeightQuery, Dependency);
            Dependency = Entities
                .WithAll<DoWorkTag>()
                .WithNone<MoveTarget>()
                .ForEach((int entityInQueryIndex, Entity entity, in LocalToWorld translation) =>
                {
//code
                })
                .ScheduleParallel(Dependency);

            // _clearTaskJob.buffer = buffer;
            // Dependency = _clearTaskJob.ScheduleParallel(_clearTaskQuery, Dependency);
        }

here it is, anyway

#

commented part in bottom

#

is one I changed for new system

#

above

#

order looks like this

solemn hollow
#

idk. seems to me like you remove the HasJobTag every frame?

rustic rain
#

nah, I only remove it

#

when Task is done

#

when MoveTarget doesn't exist

#

which is only added when this Task is assigned

#

HasJob tag just means that agent is busy with work and doesn't look for it

solemn hollow
#

So that means youd need to do that for every action your agent can do? if you had a AttackTarget Component youd have to define another query that removes HasJob when it the entity had no AttackTarget?

#

You probably should work with less tagging

rustic rain
#

HasJob is common between all

#

it's protected component inherited

#

from abstract system

#

it just means

#

that job searcher doesn't need to look for job

solemn hollow
#

yes but my point is there are many types of jobs it could have. not only MoveTarget

rustic rain
#

MoveTarget is not really job

#

it's just condition

#

for work* to be over

#

once MoveTarget is removed - means target reached point

#

it's pretty simple atm

#

since I just want to get on track

solemn hollow
#

yes but you have other jobs with other conditions

rustic rain
#

of pattern

#

yeah

#

and every condition for each will be different

#

doesn't mean it will even require tags

#

it can be any code

#

but agent will look for new work only when HasJob is absent

solemn hollow
#

so wait is that in your utility ai or is it just your logic to execute decided actions?

rustic rain
#

and it's up to work system to decide when that happens

#

MoveTarget is internal logic for agent to simply move to some position

#

not really job tag

#

each work has it's own tag though

solemn hollow
#

What i am basically asking is if you coupled your Action Logic to your AI Decision Logic

#

Sry super tired. might be a bit unclear

rustic rain
#

eh, not sure what that means

#

I can simply show code

#
        private partial struct DetermineBestActionJob : IJobEntity
        {
            public EntityCommandBuffer.ParallelWriter buffer;

            void Execute([EntityInQueryIndex] int entityInQueryIndex, Entity e, ref DynamicBuffer<ActionWeight> actions)
            {
                float highestWeight = -1f;
                int ind = -1;
                for (int i = 0; i < actions.Length; i++)
                {
                    var action = actions[i];
                    if (highestWeight < action.weight)
                    {
                        highestWeight = action.weight;
                        ind = i;
                    }
                }

                if (ind == -1) return;

                buffer.AddComponent(entityInQueryIndex, e, actions[ind].type);
                buffer.AddComponent<HasJob>(entityInQueryIndex, e);
                actions.Clear();
            }
        }

        protected override void OnCreate()
        {
            base.OnCreate();
            _findTaskJob = new DetermineBestActionJob();
            _findTaskQuery = GetEntityQuery(ComponentType.Exclude<HasJob>(), ComponentType.ReadOnly<ActionWeight>());
        }

        private DetermineBestActionJob _findTaskJob;
        private EntityQuery _findTaskQuery;

        protected override void UpdateWithBuffer(EntityCommandBuffer.ParallelWriter buffer)
        {
            _findTaskJob.buffer = buffer;
            _findTaskJob.ScheduleParallel(_findTaskQuery);
        }
    }
#

that's it

#

this is how I determine which work for agent to do

#

to add behaviour to agent you simply add components to him

#

if agent has no HasJob all it's possible actions write to ActionWeight buffer it's worth value

#

and then tag of that exact action and HasJob are assigned to agent

#

which starts Action logic

#

and cleaning job that looks for condition to remove HasJob and action tag is waiting for it to trigger

#

once that happens -> go to step 1

solemn hollow
#

ok i get it now. very similar to what i did

#

i just dont tag AIs that have completed their task. my tasks are always interuptable if something more important is dedided

rustic rain
#

I don't tag them either, I simply remove tags

solemn hollow
#

i think this shouldnt be a major performance impact no matter how you remove/add that tag. probalby for easier maintainability you should do it directly in the system that decides that the job is complete

rustic rain
#

I do also think about interruptability

#

not sure how to implement tho

solemn hollow
rustic rain
#

one thing about it

#

is to not use HasJob tag

#

but instead let decider jobs rn every update

#

but that will question perfomance

solemn hollow
#

yes thats what i do. decide every frame and if there are better actions you switch

#

makes for more responsive AI

#

But you can do both. have actions that need to be finished and ones that can be "overruled"

rustic rain
#

or maybe have interruptable timer

solemn hollow
#

What i additionaly have is a "momentumFactor" that get multiplied onto the Score and that factor decays over time

rustic rain
#

as in, some timer which will decide when job can be checked for interruption

#

for every action

#

yeah

#

I guess we think the same way about it kek

solemn hollow
#

so that when a new decision would be decided in the next frame it wont overrule the previous one because of the additional score

#

some form of hysteresis

misty wedge
#

Adding / removing tags can be a bigger performance bottle neck than just running the system every frame

#

If you have a lot of actors and it is slowing down the game, only update actors every second update

#

so 50% this frame, 50% next frame, repeat

#

And scale to actor count appropriately

solemn hollow
rustic rain
#

I think I'll have around thousand or two of agents

misty wedge
#

Really depends on how heavy the workload for them is, whether or not that will fit in a single frame

rustic rain
#

adding/removing tags only happens about once in several thousand updates

misty wedge
#

That's definitely fine, I didn't read into completely how you are doing it, I just wanted to mention it

rustic rain
#

so I think it just will take actual test

#

to find out

#

whether is better

solemn hollow
#

you wont find a diffrence in actual gameplay. since you wont have many agents in the same frame change states

#

so batching those commands wont be that helpful

#

just remove the components in commandbuffers so you can do it directly in your ActionLogic

viral sonnet
rustic rain
#
        private partial struct SetWeightJob : IJobEntityBatch
        {
            [ReadOnly] public NativeMultiHashMap<int, TargetData> hashMap;
            [ReadOnly] public SharedComponentTypeHandle<InSystemComponent> inSystemTypeHandle;
            [ReadOnly] public ComponentTypeHandle<LocalToWorld> ltwTypeHandle;
            public BufferTypeHandle<ActionWeight> bufferTypeHandle;

            public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
            {
                int systemID = batchInChunk.GetSharedComponentIndex(inSystemTypeHandle);
                var buffers = batchInChunk.GetBufferAccessor(bufferTypeHandle);
                var ltws = batchInChunk.GetNativeArray(ltwTypeHandle);
                for (int i = batchIndex; i < batchInChunk.Count; i++)
                {
                    TargetData targetData;
                    NativeMultiHashMapIterator<int> iterator;
                    if (hashMap.TryGetFirstValue(systemID, out targetData, out iterator))
                    {
                        float3 pos = ltws[i].Position;
                        pos.z = 0;
                        do
                        {
                            float3 curTarPos = targetData.position;
                            curTarPos.z = 0;

                            if (math.distance(pos, curTarPos) > 2f)
                            {
                                buffers[i]
                                    .Add(new ActionWeight() {type = ComponentType.ReadOnly<DoWorkTag>(), weight = 1f});
                                return;
                            }
                        } while (hashMap.TryGetNextValue(out targetData, ref iterator));
                    }
                }
            }
        }

Hmmmmmmmmmmm

#

smth VERY weird with this system

#

ooooh

#

I'm dum

#

very dum

#

nvm any of that

#

I do struggle regarding on point tho:
How do I move that return to continue of for loop?

#

since I also need loop for hashMap iteration

solemn hollow
#

u mean u want to break instead of return?

rustic rain
#

I just need additional method

#

to be able to leave from entity iteration

viral sonnet
#

where you return right now, you want to break the do/while but continue the for?

misty wedge
#

If I have a job that is scheduled with WithStructuralChange, but there are 0 entities in that query, does it still cause a sync point?

rotund token
#

Good question, can't remember

#

I'd usually early out with an is empty ignore filter anyway to avoid it

rustic rain
#

hmmm

#

Are structural changes in ECB performed in any order they were scheduled?

#

nvm, I don't think it matters anyway

viral sonnet
#

shame there's no burstable navmesh :/

rotund token
#

do you mean building it?

#

because you can query unity navmesh from burst jobs

#

i have my own wrap of recast though

#

doesn't perform as well as i'd like for local updates - it's fine just not as good as i'd like

#

not sure what magic unit made to theirs

viral sonnet
#

yeah, actually just querying. oh that works. have to look into it

rotund token
#

they used navmesh from burst jobs in their first austin demo

viral sonnet
#

cool, thanks! then I even have source code

rotund token
#

if you look at NavMeshQuery

#

it's just a pointer

#

wrote a how to way back in 19 on how to build it

rustic rain
#

hmm, I noticed that IJobEntity in System debugger shows a lot of fake queries

#

with fake entities

#

well, not really fake

#

but it counts same entities several times

#

is there any kind of way to avoid that?

rotund token
#

screenshot?

#

kind of confused unless you are talking about multiple queries being generated for a single IJobEntity

rustic rain
#

I have only 1800 total

#

wait

#

actually I only have 900

rotund token
#

seems to imply you have 937 huggers

rustic rain
#

939

#

total

rotund token
#

πŸ€”

rustic rain
#

937 huggers, 1 dynamo, 1 player

#

irrelevant

rotund token
#

so what's your second sphere query doing

rustic rain
#

which one?

#

the one with 2800

rotund token
#

yes

#

i'm confused about your problem

#

your job query is only returning the 937 entities you seem to want

rustic rain
#

it looks like this, but I use EntityQuery

#

oh wait

rotund token
#

but you have a second query in your system that is returning every entity with LocalToWorld

rustic rain
#

I meant this

#

the one above is totally different system, kek

#

_doWorkQuery = GetEntityQuery(ComponentType.Exclude<GetToPoint>(), ComponentType.ReadOnly<DoWorkTag>());
I only schedule it with query

rotund token
#

seems like IJobEntity always includes it's own query

#

even if you don't use it

#

i guess Entities.ForEach didn't allow you to use your own query so it's an oversight

#

that's not ideal

viral sonnet
#

I still find it odd that you need to explicitly set read/write not just on the job but also in a query. Weird redundancy.

viral sonnet
#

oh wow, just found out my exposed CDFE was missing AddReaderWriter and not setting dependencies. how did this work out for so long without any errors I wonder ... πŸ˜„

rotund token
rustic rain
#

Where can I find examples on generic jobs?

#

I got to the situation, where I got 2 jobs that look literally equal

#

lol

#

allthough...

#

not really equal

#
            [ReadOnly] public ComponentDataFromEntity<LocalToWorld> translations;
            public EntityCommandBuffer.ParallelWriter buffer;

            private void Execute([EntityInQueryIndex] int entityInQueryIndex, Entity e, ref Translation translation,
                ref Rotation rotation,
                in FollowShip followShip, in ShipSpeed speed, in Maneuverability maneuverability)
            {
                float3 targetPosition = translations[followShip.entity].Position;

                MovementJobUtility.CalculateDelta(targetPosition,
                    translation.Value,
                    out float3 delta,
                    out float deltaSpeed);

                if (deltaSpeed < 1f)
                {
                    buffer.RemoveComponent<FollowShip>(entityInQueryIndex, e);
                    buffer.RemoveComponent(entityInQueryIndex, e, followShip.tagsToRemove);
                }
                else
                {
                    deltaSpeed = speed.value;
                }

                MovementJobUtility.CalculateShipMovement(ref translation,
                    ref rotation,
                    delta.Normalized(),
                    deltaSpeed,
                    maneuverability.value);
            }
#
            Entities.ForEach(
                    (int entityInQueryIndex, Entity e, ref Translation translation, ref Rotation rotation,
                        in GetToPoint target, in ShipSpeed speed, in Maneuverability maneuverability) =>
                    {
                        MovementJobUtility.CalculateDelta(target.value,
                            translation.Value,
                            out float3 delta,
                            out float deltaSpeed);

                        if (deltaSpeed < 1f)
                        {
                            buffer.RemoveComponent<GetToPoint>(entityInQueryIndex, e);
                            buffer.RemoveComponent(entityInQueryIndex, e, target.tagsToRemove);
                        }
                        else
                        {
                            deltaSpeed = speed.value;
                        }

                        MovementJobUtility.CalculateShipMovement(ref translation,
                            ref rotation,
                            delta.Normalized(),
                            deltaSpeed,
                            maneuverability.value);
                    })
                .ScheduleParallel();
#

not sure if I can turn it into one

rotund token
#

at the very least create a helper struct for the identical code

#

ShipUtility or something

muted field
#

does unity plan to add a stopwatch feature for jobs to get timings - profiler is okay but its kind've annoying some times to navigate πŸ˜„

rotund token
#

probably not but you can add your own if you really want

#

though i'd just use profiler

#

but to quote myself

#
https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
    [DllImport("Kernel32.dll", CallingConvention = CallingConvention.Winapi)]
    private static extern void GetSystemTimePreciseAsFileTime(out long filetime);
Or other platform specific methods or you can just write your own little native library to provide all the functionality you need.```
solemn hollow
rustic rain
#

ngl

#

I hate the way I did it

#

want to redo it completely

#

Can't figure out yet how

#

I need some kind of system that can make any sorts of tasks and action

#

including chained

#

from simple: get to point
to: do a system jump to other star system, land on planet, do X thing

muted field
rotund token
#

you need to use profilermarkers

#

if you want to profile inside burst jobs

rustic rain
#

I did figure out that I can add logic on end to add/remove components, which will let me chain events
But I just don't see yet how it would work

muted field
#

oh i found them they just absolutely tiny =/

#

had to zoom in so far just to actually see them properly

rotund token
#

if your jobs are that fast

#

do you really need to profile them

#

πŸ˜„

muted field
#

well it says 0.001ms but im thinking that might just be the smallest number it will show πŸ˜›

#

ive never seen anything with a smaller number

rotund token
#

1us is about the smallest precision you're ever going to get

muted field
#

is that because of profiler performance reasons or a C# limitation ?

rotund token
#

computer/os limitations

muted field
#

oh lol

#

well that is stupidly fast for a path finder im almost believing i must've done something wrong for it to be that fast

rustic rain
#

or it's just burst

#

which is known for doing magic with pathfinding

#

kek

rotund token
#

at that time it's not doing anything

solemn hollow
rotund token
#

personally my AI has no knowledge of pathfinding except to request either a static or dynamic path

#

requests a path to X and then the pathfinding / movement system/s take over

#

and just feed back any data the AI might need to make decisions

rustic rain
rotund token
#

Unavailable/AtDestination/Processing/InProgress/etc

solemn hollow
#

yep thats how i do it too

muted field
#

i have to make a decision now on whether to use managed objects and create temporary arrays of unmanaged data to do jobs or keep everything unmanaged

solemn hollow
#

I only have different Actions for Movement when it comes to fighting vs move to job. Since when fighting i want them to use diffrent speed etc.

rustic rain
#

rn I want to make 2 basic actions:
Get to Point
Follow Ship

first is controlled by pepega AI which just moves entity to next point of array (next quadrant)

Second is making entity to follow player ship, until it's too close -> and do pepega AI if it is

#

so, I guess you suggest to make FollowShip action just correct GetToPoint target value?

solemn hollow
muted field
#

well i played around with pointers so i could update structs without worrying about defensive copies etc. which is a big plus, but things can go wrong with pointers if i screw it up very quickly

solemn hollow
rustic rain
#

I'm not sure how chaining will work

#

So hugger AI wants every hugger to go and hug player once, but only if it's too far (meaning if it's close, action weight will be low)

#

In previous scenario

#

I'd simply add FollowShip component with entity field of player

#

and all moving logic is in here

#

once it reaches player -> FollowShip along with HasJob gets removed, and agent returns to AI pool

solemn hollow
#

The great part about utility AI is that you dont need to define chaining explicitly

rustic rain
#

then what would be the way to achieve that behaviour?

solemn hollow
#

so lets say you have an Agent that should gather resources.
You dont need to make that one single decision with an Actionchain.
the Actionchain looks like this:

  1. GoToTarget (Resource)
  2. FillBackpack with resource
  3. GoToTarget (ResourceDepot)
  4. EmptyBackpack

now your decisionlogic can decide when to execute what. at any point in time it doesnt need to know that you decided to GatherResources but only needs to execute its step until another Action automatically takes over.

#

So your first Action is taken when resources are low.
Second Action is taken when backpack is empty and you are close to resource.
Third Action is taken when backpack is full
Forth Action is taken when Backpack is full and close to Depot.

#

Now if anything ingame would happen that would fill the backpack the agent will move to the ResourceDepot.

#

it wouldnt have to first gather resources to drop them afterwards.

rotund token
#

or outside sources like being attacked might cause it to interrupt and start fighting

muted field
#

you doing GOAP ai then ?

#

or using unity's ai planner?

rotund token
#

and when it's done, it'll just naturally go back to moving to resource depot

rustic rain
#

ok, I see the point

solemn hollow
#

This is how GoToAttackPosition is defined in my AI. you see the responsecurve of the first Axis? that just tells the agent to not go closer if close enough and then attack takes over

rustic rain
muted field
solemn hollow
#

this is how attack then looks like

#

When close to a Zombie and if healthpoints are high it attacks.

#

if health would be low i have another Consideration that triggers a Flee

rustic rain
#

what is that? how did you make it look like this?

#

kinda looks like ScriptableObject

#

for OOP

solemn hollow
#

yes i author the whole AI with scriptable objects and Graphs in the editor.

rotund token
rustic rain
#

I kind of want to learn how to do same thing kek

#

any examples?

solemn hollow
#

WDYM? Its just a Scriptable Object which i convert into data my ConsiderationSystem gets to run its logic.

rustic rain
#

ah

rustic rain
# solemn hollow

sir, could you explain how exactly do you chain those considerations into one action?

#

As in architecture

#

I do get how to make consideration of one

#

but combine 2 (or more) dynamically, is smth different

solemn hollow
#

@rustic rain Not sure if i understand your question. The Consideration with the highest UtilityScore gets Chosen as the Decision. The Action that is defined in the Decision is executed.
Your Considerations should be set up in such a way that they make sense even if not used in a chain of actions. A chain of Actions is something emergent of the System of all of those considerations combined.

rustic rain
#

I mean, considerations

#

so far all my considerations

#

are single job hard coded

#

unique per action

#

but from what I see

#

you somehow managed to do it in a dynamic way

#

where you add several consideration to any action

#

or am I wrong?

solemn hollow
#

hmm maybe the terminology is a bit confusing in my case because considerations dont have to be coupled to Actions but can be. I assign an Action to a consideration not the other way around. So i can assign the GoToTarget Action to different Considerations that result in diffrent Targets to go to.

#

i think you confuse the Axis with considerations? Axis are the Input to the Consideration. Each Axis gets multiplied with one another to get the UtilityScore of the Consideration

rustic rain
#

consideration is 0..1 of Action weight?

#

axis is input of consideration?

#

in that case, I meant axis, yes

solemn hollow
#

What you refer to as Action weight i refer to as Utility Score if that makes it a little bit clearer

rustic rain
#

all right

solemn hollow
rustic rain
#

I get how it works on paper, but I don't see it in ECS

solemn hollow
#

Yes making it generic in ECS was the hardest part.

#

i scrapped like 3-4 designs cause i always ran into another roadblock

rustic rain
solemn hollow
#

I mean if i do its like forcing you into a direction you might not want to go with it. The decision on how you implement it will change performance and scalability characteristics. Mine is not as performant as other possible implementations because in mine i have to run a System for each consideration which costs mainthread time. In other implementations you could possibly run a single system on mainthread and evaluate all considerations on workers. (I cant do that because for me execution order is crucial)
So as soon as i show you some of my code you are locked into some form of my implementation.

#
public class ConsiderationSys<C, CP, A0, A1, A2, A3> : BaseConsiderationSystem<C, CP>
    where C : struct, IBufferElementData, IConsideration
    where CP : struct, IBufferElementData, IConsideration
    where A0 : struct, IBufferElementData, IConsideration
    where A1 : struct, IBufferElementData, IConsideration
    where A2 : struct, IBufferElementData, IConsideration
    where A3 : struct, IBufferElementData, IConsideration

{
    protected override void OnUpdate()
    {
        var decisionRequestBufferHandle = GetBufferTypeHandle<DecisionRequest>();

        var chunks = considerationQuery.CreateArchetypeChunkArray(Allocator.TempJob);
        var considerationOutputHandle = GetBufferTypeHandle<C>();

        
        var axis0Handle = GetBufferTypeHandle<A0>(true);
        var axis1Handle = GetBufferTypeHandle<A1>(true);
        var axis2Handle = GetBufferTypeHandle<A2>(true);
        var axis3Handle = GetBufferTypeHandle<A3>(true);

        Dependency = new GenConsiderationJob
        {
            chunks = chunks,
            considerationOutputHandle = considerationOutputHandle,
            systemSettings = systemSettings,
            entityTypeHandle = GetEntityTypeHandle(),
            
            targetFilterMask = targetFilterMask,
            axis0Handle = axis0Handle,
            axis1Handle = axis1Handle,
            axis2Handle = axis2Handle,
            axis3Handle = axis3Handle,
        }.ScheduleParallel(chunks.Length, 1, Dependency);

        Dependency = new OutputBufferToDecisionJob<C>()
        {
            decisionReqHandle = decisionRequestBufferHandle,
            chunks = chunks,
            considerationOutputHandle = considerationOutputHandle,
            systemSettings = systemSettings,
            entityTypeHandle = GetEntityTypeHandle(),
        }.ScheduleParallel(chunks.Length, 1, Dependency);
        base.OnUpdate();
    }
#

This is how my ConsiderationSystem looks like. I Systems like that for up to 10 Axis.

#
private struct GenConsiderationJob : IJobFor
    {
        public BufferTypeHandle<C> considerationOutputHandle;
        [ReadOnly] public EntityTypeHandle entityTypeHandle;
        
        [ReadOnly] public EntityQueryMask targetFilterMask;
        [ReadOnly]public NativeArray<ArchetypeChunk> chunks;
        [ReadOnly]public BlobAssetReference<ConsiderationSettingsBlobAsset> systemSettings;
        [ReadOnly]public BufferTypeHandle<A0> axis0Handle;
        [ReadOnly]public BufferTypeHandle<A1> axis1Handle;

        public void Execute(int chunkIndex)
        {
            var entityChunkArray = chunks[chunkIndex].GetNativeArray(entityTypeHandle);
            var chunkConsiderationOutputData = chunks[chunkIndex].GetBufferAccessor(considerationOutputHandle);

            var chunkDataAxis0 = chunks[chunkIndex].GetBufferAccessor(axis0Handle);
            var chunkDataAxis1 = chunks[chunkIndex].GetBufferAccessor(axis1Handle);
             
            for (int iEntityInChunk = 0; iEntityInChunk < entityChunkArray.Length; iEntityInChunk++)
            {
                var currentEntity = entityChunkArray[iEntityInChunk];
                var considerationOutputBuffer = chunkConsiderationOutputData[iEntityInChunk];

                var genericAxisArray = new UnsafeList<DynamicBuffer<Consideration<GenericAxis>>>(10, Allocator.Temp)
                {
                    chunkDataAxis0[iEntityInChunk].Reinterpret<Consideration<GenericAxis>>(),
                 chunkDataAxis1[iEntityInChunk].Reinterpret<Consideration<GenericAxis>>()
}                
                ConsiderationUtility.CalculateConsiderationValues(currentEntity, considerationOutputBuffer,
                    systemSettings, genericAxisArray, targetFilterMask);
            }
        }
    }
#

thats how a job looks like that i schedule from those ConsiderationSystems.Hope that shows some architecture. All the magic happens inside ConsiderationUtility.CalculateConsiderationValues

rustic rain
#

How does each AXIS access required components?

solemn hollow
#

each axis is a DynamicBuffer on the AgentEntity. How that data gets to the AgentEntity in the first place is in a diffrent Step in my Architecture.

rustic rain
#

well, that's the most interesting part

#

since my thinking rn is only limited by fixed Queries

#

and hard coded argument lists

solemn hollow
#
public class BaseScoringSys<C, D> : BaseScoringSys <C>
    where C : struct, IBufferElementData, IConsideration
    where D : struct, IComponentData, IVectorizable
{
    protected override void OnStartRunning()
    {
        vectorizerQuery = GetEntityQuery(systemSettings.vectorizerFilter);
        vectorizableQuery = GetEntityQuery(systemSettings.vectorizableFilter);
        vectorizerQuery.ResetFilter();
        vectorizerQuery.AddChangedVersionFilter(typeof(LoadBalanced));
    }
    protected override void OnUpdate()
    {
        var vectorizerChunks = vectorizerQuery.CreateArchetypeChunkArray(Allocator.TempJob);
        var vectorizableChunks = vectorizableQuery.CreateArchetypeChunkArray(Allocator.TempJob);

        Dependency = new ScoringJob
        {
            entityTypeHandle = GetEntityTypeHandle(),
            vectorizableHandle = GetComponentTypeHandle<D>(true),
            vectorizerChunks = vectorizerChunks,
            vectorizableChunks = vectorizableChunks,
            curveParameters = systemSettings.responseCurve.parameters,
            vectorizerOutput = GetBufferTypeHandle<C>(false),
            minVal = systemSettings.minValue,
            maxVal = systemSettings.maxValue,
            scoringType = systemSettings.scoringType,
            outputProcessor = systemSettings.outputProcessor,
        }.ScheduleParallel(vectorizerChunks.Length, 1, Dependency);
    }
}```
#

this is how that first step in the most basic form looks like

rustic rain
#

hm, so basically generic typeHandles

solemn hollow
#

this is how its authored. The query which entities i wanna read from is defined in that scriptable object.

#

So in this first "vectorization" step i read from other entities and write to the AgentsDynamicBuffers.

#

In the next step i use those buffers as inputAxis for the ConsiderationSystems

rustic rain
#

smth weird, kek

#

so you have dynamic buffer

#

which defines up to 10 axis

solemn hollow
#

thats why i said you shouldnt go with my implementation of it. i made those decisions for very specific reasons ^^

rustic rain
#

is that correct?

solemn hollow
#

yes

rustic rain
#

yeah, I'm not sure if I need this one

#

or Utility AI at all

#

kek

solemn hollow
#

well one consideration can have up to 10 axis as input to be more precise

rustic rain
#

But it doesn't hurt to know

#

of options

#

Utility AI seems like a great solution for situational AI

#

but I'm really uncertain about sim kind of AI

#

Allthough

#

It's just very different way of thinking, like OOP vs ECS

solemn hollow
#

yes i agree. im doing sim stuff with it though

#

literally sims is using utilityAi btw πŸ˜„

rustic rain
#

I know

#

I just need a lot of time

#

to think it through

#

feels bad there are very little examples

rustic rain
#

will it error if I SetComponentData on entity that doesn't have component?

#

I noticed, that it doesn't error if I add on ones that already have it

#

I just want to be certain, that I have SetOrAdd method for it

solemn hollow
#

Add essentially is SetOrAdd

rustic rain
#

oh

#

that's great

#

Is there a way to get ref to some component in NativeArray?

#

I have quite a lot of arrays to process xD

#

kind of wish I could just send them all with ref in case I need to write them

solemn hollow
#

you should be able to write to them

north bay
# rustic rain kind of wish I could just send them all with `ref` in case I need to write them
    [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
    private static unsafe void CheckIndexInRange(int index, int length)
    {
        if (index < 0 || index > length)
            throw new ArgumentException( $"Index {index} is out of range of length {length}" );
    }
    
    public static unsafe ref T GetAsRef<T>( this NativeArray<T> array, int index ) where T : struct
    {
        CheckIndexInRange( index, array.Length );

        return ref UnsafeUtility.ArrayElementAsRef<T>( array.GetUnsafePtr( ), index );
    }
rustic rain
#

yeah, but I seek for efficiency

north bay
#

That's copied out of my toolbox

rustic rain
#

oh

#

wow, seems to work

#

thanks

#

any idea tho, whether it's perfomance efficient? xD

#

I assume it doesn't make any difference

#

in long shot

#

but still

solemn hollow
#

and you shouldnt care now. make it run then optimize if you actually see it in profiler...

rustic rain
#

I do wonder tho, whether it actually gets ref

#

since if Component is large

#

that would actually make a difference

#

I think...

solemn hollow
#

and meanwhile you probably mess up some smart optimizations burst does for u

rustic rain
#

maybe

#

damn

solemn hollow
#

you have enough other problems to worry about. those microoptimizations are never worth your time

north bay
rustic rain
#

any idea how it interacts with burst and etc?

north bay
#

What exactly do you mean? It works just fine in bursted jobs

frigid crypt
#

Does anyone know if there is any up to date tutorial about triggers in DOTS?

rustic rain
#
                .ForEach((CompanionGameObjectComp link, in LocalToWorld localToWorld) =>
                {
                    link.companion.transform.position = localToWorld.Position;
                    link.companion.transform.rotation = localToWorld.Rotation;
                })
viral sonnet
rustic rain
#
            public void Execute(ArchetypeChunk batchInChunk, int batchIndex)
            {
                var entities = batchInChunk.GetNativeArray(entityTypeHandle);
                var translations = batchInChunk.GetNativeArray(translationTypeHandle);
                var rotations = batchInChunk.GetNativeArray(rotationTypeHandle);
                var gtps = batchInChunk.GetNativeArray(gtpTypeHandle);
                var speeds = batchInChunk.GetNativeArray(speedTypeHandle);
                var maneuverabilities = batchInChunk.GetNativeArray(maneuverabilityTypeHandle);

                for (int i = 0; i < entities.Length; i++)
                {
                    var e = entities[i];
                    ref var target = ref gtps.GetAsRef(i);
                    ref var translation = ref translations.GetAsRef(i);
                    ref var rotation = ref rotations.GetAsRef(i);
                    if (target.follow != Entity.Null)
                    {
                        target.value = translation.Value;
                    }

                    MovementJobUtility.CalculateDelta(target.value,
                        translation.Value,
                        out float3 delta,
                        out float magnitude);

                    if (magnitude < 1f)
                    {
                        buffer.RemoveComponent<GetToPoint>(batchIndex, e);
                        buffer.RemoveComponent(batchIndex, e, target.tagsToRemove);
                    }

                    rotation.Value = MovementJobUtility.CalculateRotation(delta.Normalized(),
                        maneuverabilities[i].value,
                        rotations[i].Value);

                    translation.Value += MovementJobUtility.CalculateMovement(rotation.Value, speeds[i].value);
                }
            }

This system breakes LTW component

#

I'm not sure, maybe it's because of this ref

#

feels bad

#

nnnope

#

same error even without refs

viral sonnet
#

do you use physics?

north bay
#

What does CalculateRotation do and is .Normalized using normalize or normalizesafe?

viral sonnet
#

in case you do add this protected override void OnStartRunning() { this.RegisterPhysicsRuntimeSystemReadOnly(); }

north bay
rustic rain
#

hmmm

#

I think

#

it's my logic breaking it

#

smth about 0

viral sonnet
#

oh sorry, didn't see the posted error. lol I have never seen this

rustic rain
#

since I found out most of those errors are from

#

delta = 0,0,0

north bay
#

If you use normalize try using normalizesafe, normalize will cause nans when your input is zero

rustic rain
#
        public static float3 Normalized(this float3 original)
        {
            return original / (math.sqrt(original.x * original.x + original.y * original.y + original.z * original.z));
        }
#

this is how I normalize

viral sonnet
#

the ref usage is inconsistent. you get a ref but then use it as value in the method call

north bay
#

You are dividing by zero or not (or by nan lol)?

viral sonnet
#

but that has nothing to do with the actual error

rustic rain
rustic rain
#
                    if (target.follow != Entity.Null)
                    {
                        target.value = translation.Value;
                    }
#

Commenting this

#

solved most problems

#

but 1 still errored

#
        public static quaternion CalculateRotation(float3 directionNormalized, float maneuverability,
            quaternion curRotation)
        {
            quaternion tarRot = quaternion.LookRotation(forward, directionNormalized);
            return math.nlerp(curRotation, tarRot, maneuverability);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static float3 CalculateMovement(quaternion rotation, float speed)
        {
            return math.mul(rotation, up * speed);
        }

here those methods

viral sonnet
#

I find it easier to get refs with just the pointers. doesn't need 2 method calls and the length check is redundant. burst should inline the methods but adding a [MethodImpl(MethodImplOptions.AggressiveInlining)] also helps in case burst doesn't

north bay
viral sonnet
#

as a generic method it's totally okay. if you are iterating over chunks it's not needed though, might write a method without the check. if conditions are expensive and non-vectorizable

#

burst has a much easier time though with using pointers I have learned. many codes parts were not vectorized with a struct array but easily did with a struct pointer

#

that's not much of bursts doing, I think it's the LLVM optimizations

rustic rain
#

What is the way to get parallel write access to certain Component?

viral sonnet
#

For NativeContainers that prevent parallel writing there's a tag: [NativeDisableParallelForRestriction]

rustic rain
#

so, I just need to be certain that I don't write to same entity slot?

#

I use it to read global data by entity. And then write to it's iterated entity
public ComponentDataFromEntity<Translation> translations;

north bay
viral sonnet
#

It does, but the assembly often doesn't show any vectorization. kornflaks went pretty hardcore into it and helped me a bunch of times. There are some very weird quirks. Like, it's better to process everything in another Method than the normal Execute method.

viral sonnet
rustic rain
#

uuuuugh

#

for some reason, instead of going to that one capsule in middle (player)

#

all these guys are just running in circles

#

even though judging by logic they should constantly try to rotate towards that player

#
                    float3 delta = target.value - translation.Value;
                    delta.z = 0;
                    float magnitude = delta.Magnitude();


                    if (magnitude < target.distance)
                    {
                        buffer.RemoveComponent<GetToPoint>(batchIndex, e);
                        buffer.RemoveComponent(batchIndex, e, target.tagsToRemove);
                    }

                    quaternion tarRot = quaternion.LookRotation(MathUtility.forward, delta.Normalized());
                    rotation.Value = math.nlerp(rotation.Value, tarRot, maneuverabilities[i].value);

                    translation.Value += math.mul(rotation.Value, MathUtility.up * speeds[i].value);
                    translations[e] = translation;

Here code once again, unwrapped from utils

#

target.value is player's position

#

ok

#

nothing wrong with logic

#

smth wrong with components?

#

basically, player entity supposed to go to one of those yellow entities
But instead, it's target is near him

#

even though, it's supposed to be equal of yellow entity

#
                    if (target.follow != Entity.Null)
                    {
                        target.value = translations[target.follow].Value;
                    }
#

I don't understand

#

is there any possible reason why Translation given can be that wrong

pliant pike
#

componentdata can sometimes not update immediately in the editor

rustic rain
#

It's not editor

#

it's world, which is working not the way I expect

solemn hollow
#

are your entities moving?

#

and are you maybe reading some kind of localTranslation instead of world translation

rustic rain
#

yeah

#

they are constantly running in circles

#

that position is going in circles

pliant pike
#

I had an issue before where the object to move towards was a child of another which was offset

rustic rain
#

entities can only move forward, based on rotation

solemn hollow
#

i suspect it is what calabi is saying

rustic rain
#

not LocalToWorld

#

hm

#

I'll try to switch

pliant pike
#

I had to make sure all the objects were the same root position

rustic rain
#

it's just that LocalToWorld is not that fun to read

#

write*

solemn hollow
#

the problem is translation is in parentspace

pliant pike
#

yeah you have to be careful with child and parents and making sure there transforms are all similar if you want to move towards them

rustic rain
#

yep, still that problem

#

you might notice 3 entities on bottom

#

are facing that wrong point

#

meanwhile they should face player in middle

#

this time

#

I read LocalToWorld

#

red line should be the their facing direction

#

yet they go to that weird point somehwere else

pliant pike
#

try resetting the transform of the player and all its children

rustic rain
#

wdym?

#

If I move

#

player to somewhere else

#

they just pick some other point nearby

#

player

#

same thing

pliant pike
#

so movement is wrong also?

rustic rain
#

well, movement itself isn't

#

rotation is

#

movement code is literally just moving entity forward by amount of speed

pliant pike
#

it sounds like the child transforms are not the same as the parent and are offset in someway

rustic rain
#
                for (int i = 0; i < entities.Length; i++)
                {
                    var e = entities[i];
                    ref var translation = ref translations.GetAsRef(i);
                    ref var rotation = ref rotations.GetAsRef(i);
                    ref var target = ref gtps.GetAsRef(i);

                    if (target.follow != Entity.Null)
                    {
                        target.value = ltws[target.follow].Position;
                    }

                    float3 delta = target.value - translation.Value;
                    delta.z = 0;
                    float magnitude = delta.Magnitude();


                    if (magnitude < target.distance)
                    {
                        buffer.RemoveComponent<GetToPoint>(batchIndex, e);
                        buffer.RemoveComponent(batchIndex, e, target.tagsToRemove);
                    }

                    quaternion tarRot = quaternion.LookRotation(MathUtility.forward, delta);
                    rotation.Value = math.nlerp(rotation.Value, tarRot, maneuverabilities[i].value);

                    translation.Value += math.mul(rotation.Value, MathUtility.up * speeds[i].value);
                }
#

here all code again

rustic rain
#

here player

#

this is one of yellow

#

their ltw is different

#

ugh

#

stuped dum components

#

I'll just try to switch fully on LTW

pliant pike
#

they don't look the same to me πŸ˜•

rustic rain
#

yeah, I noticed

#

I guess

#

you were right kek

solemn hollow
#

well read from LocalToWorld and write to Translation, Rotation,Scale. thats how it is supposed to be done

rustic rain
#

hmmmm

#

I didn't know it was a thing

pliant pike
#

cool, I was right for once

rustic rain
#

yyyyep

#

switching to reading from ltw

#

fixed it

solemn hollow
#

i guess you should read some documentation πŸ˜‰

rustic rain
#

I kinda try

#

but then I literally forget most of it

#

kek

#

you mean this one, right?

solemn hollow
#

in this case i mean the Transform Package docs

rustic rain
#

oh

pliant pike
#

I wouldn't even try to read the stuff about local to world and transforms to be honest

#

I can't understand a word of it leahWTF

solemn hollow
#

Ah wait they integrated the transofrms documentaion into Entities documentation now

#

nvm then

solemn hollow
#

You dont have to understand the math to understand how to use it though. i think its pretty well written

pliant pike
#

to many words and graphs for me πŸ˜•

viral sonnet
#

I could cry everytime when I see the chunk capacity with a LTW πŸ˜„

solemn hollow
#

so you dont have a localtoworld but a translation? interesting?

rustic rain
#

impossible

#

hybrid renderer depends on it

#

feels bad ngl

#

wish we'd have 2D transform

#

or at least some neutral LTW

#

so we could write 2d ourselves

solemn hollow
viral sonnet
#

quite simple actually, only use LTW and explicitly remove Translation/Rotation on conversion. LTW has some nice methods to easily get position/rotation too

rustic rain
#

not to write them though

#

sadkek

viral sonnet
solemn hollow
#

ah thats why i was confused πŸ˜‰

viral sonnet
#

hehe yeah

rustic rain
#

wait a second

#

So does LocalToParent work fine

#

without Translation/Rotation/Scale?

pliant pike
#

everything has translation/rotation surely

solemn hollow
pliant pike
#

I mean stuff converted from gameobjects though

solemn hollow
#

i think if you use staticoptimizeentity Component only LocalToWorld is left after conversion

pliant pike
#

interesting I have used that before I did not know πŸ€”

solemn hollow
#

hmm why does this work. how do they guarantee the transform components were already added.

#

are they added in early conversion?

#

i wish there was a way to write an Entities.Foreach for the destination world instead of the Conversion world

mystic mountain
#

I have some weirdness where some parts of my Instantiated visual entities are not parented correctly, I can see them at origin. Is this something someone else has experienced?

viral sonnet
safe lintel
#

has anyone used IJobEntity inside of an ISystem successfully? getting this error Partial declarations of 'TimingSystem' must be all classes, all records, all structs, or all interfaces

solemn hollow
solemn hollow
viral sonnet
#

ah, yeah that happens internally before the Convert is called

solemn hollow
#

ah ok makes sense. Cant remember my exact usecase but i wanted to remove Components unity added somewhere in conversion and couldnt get it to work properly because of ordering. opted to do it in the first frame of the game then...

mystic mountain
frigid crypt
#

I need help before I lose my fucking mind

#

Does anyone have any idea why the score ends up not being incremented?

[BurstCompile]
    struct PickupOnTriggerSystemJob : ITriggerEventsJob
    {
        [ReadOnly] public ComponentDataFromEntity<PickUp_Tag> allPickups;
        [ReadOnly] public ComponentDataFromEntity<Player_Tag> allPlayers;
        public ComponentDataFromEntity<Player_Component> player;  
        public EntityCommandBuffer entityCommandBuffer;


        public void Execute(TriggerEvent triggerEvent)
        {
            Entity entityA = triggerEvent.EntityA;
            Entity entityB = triggerEvent.EntityB;
            if (allPickups.HasComponent(entityA) && allPickups.HasComponent(entityB))
                return;

            if (allPickups.HasComponent(entityA) && allPlayers.HasComponent(entityB))
            {
                entityCommandBuffer.DestroyEntity(entityA);
                Player_Component mod_player = player[entityB];
                mod_player.score++;
                player[entityB] = mod_player;
            }
            else if (allPlayers.HasComponent(entityA) && allPickups.HasComponent(entityB))
            {
                entityCommandBuffer.DestroyEntity(entityB);
                Player_Component mod_player = player[entityA];
                mod_player.score++;
                player[entityA] = mod_player;
            }
        }
    }
rustic rain
#

potentially they might not even trigger

frigid crypt
#

I know they are triggering because the entity does get destroyed

rustic rain
#

wait a second, what is mod_player?

#

if it's some static field

#

it won't work

frigid crypt
#

mod_player is the Player_component (from ComponentDataFromEntity) of the entity involved in the trigger event that has a Player_Tag

rustic rain
#

oh

#

me blind

#

eh, I recommend to breakpoint it anyway

pliant pike
#

is Componentdatafromentity a copy πŸ€”

#

I can't remember because I haven't used that in a while but if they are like nativearrays then you have to use a certain structure to copy to them and or copyback to the main componentdata

rotund token
#

They are writing it back

pliant pike
#

ok cool, figured I check for the most basic thing

rotund token
#

Yeah makes total sense and was my thought as well

#

But seems like something odd is happening elsewhere

frigid crypt
#

Ok, I'm a fucking idiot

#

I was reading the score from a copy that I forgot to keep updating after the initial assigment

muted field
#

will this remain a valid pointer after my function return:

public unsafe class Test : MonoBehaviour
{
    private Segment _segment;

    private Segment* _ptr;
    public Segment* Ptr => _ptr;


    private void Awake()
    {
        _segment = new();
        _ptr = (Segment*)UnsafeUtility.Malloc(sizeof(Segment), UnsafeUtility.AlignOf<Segment>(), Allocator.Persistent);

        fixed (Segment* t = &_segment)
            _ptr = t;
    }
}

or will it not stay fixed?

#

need to make sure my pointer remains valid for as long as the gameobject exists

#

its some what unknown to me what C# does with it outside of fixed

rotund token
#

Ah no

#

You've overwritten your valid ptr

#

A pointer in fixed is only valid in fixed

muted field
#

right but outside of fixed its not valid once the function ends aswell

#

so i dunno how to keep it persistent

rotund token
#

Technically it's not valid before the method ends

#

You've allocated a block of memory with malloc then simply ignored it

#

And will be leading that

#

As you never free it

muted field
#

so how do you set _ptr to the segment i make and not lose a valid pointer

#

would i have to set each field of _ptr manually without the ctor call and that will remain valid due to it being a class member scope?

#

until the gameobject is destroyed of course

rotund token
#

you can not have ptr to _segment unless it's either a class and you tell the GC to pin it or if it's a reference then you tell the GC to pin Test

muted field
#

ah its a struct in this case as i use it for jobs

rotund token
#

i'd seriously take a step back and consider what you're trying to do as it doesn't really make much sense

#

you're either going to spend months debugging segfaults, memory leaks or concurrency issues or more likely, all of them

muted field
#

well my job returns a list of segments but i have no way to know which monobehaviour they link to. so by using pointers i can change the segment data from the results without needing to know what gameobject they link to

#

since structs cant even have a reference to a monobehaviour they related to because of jobs

#

when i edit a segment after the job result, my monobehaviour then updates the mesh to match the segment changes

#

which is how they link together

#

do you never have any data tied to monobehaviours when you use jobs ? or you used entities for it all ?

rotund token
#

i never use monobehaviours in my own projects

#

as for hybrid work, i strongly advice people never to call entity code from monobehaviours, only the other way around

muted field
#

ah for me i only need this one aspect using jobs so using entities is a full rewrite and not worth it

rotund token
#

if you are not using entities then the general approach for using jobs is to populate native arrays from your monobehaviour data

#

pass it to jobs

#

then read back the results and write it back to the monobehaviours

muted field
#

but how do you know which data goes to which monobehaviour

#

since you can't have a field in the structs containing a reference to a monobehaviour

rotund token
#

the monobehaviour that rights to index 5

#

is the monobehaviour that reads from index 5

muted field
#

oh so link by indices

#

that could be messy when removing elements from a list however

#

i really hope the release of 1.0 will have some good written tutorials to explain how to do a hybrid approach like this

#

i strongly suspect they will just focus on entities however

rotund token
muted field
#

i would need to go through them all and change the index in the array so the monobehaviour still has the right index it connects to

rotund token
#

var data = new NativeArray<Segment>(monos.Length, Allocator.TempJob);

for(var i = 0; i < monos.Length; i++)
{
    data[i] = monos.GetBurstData();
}

var output = new NativeArray<Output>(monos.Length, Allocator.TempJob);

new Job {
    Input = data,
    Output = output,
}.Schedule().Complete();

for(var i = 0; i < monos.Length; i++)
{
    monos.WriteResult(output[i]);
}

output.Dispose();
Data.Dispose();```
#

this is what i would consider a 'general' practice of executing work from monobehaviour in a job

muted field
#

oh i see

rotund token
#

i don't know if you have access to a* pathfinding

#

but it has some really good examples with it's RVO simulation

#

which takes actors that are monbehaviour

#

passes them to a simulation in a collection of jobs

#

and writes it back

muted field
#

you mean the asset A* pathfinding project ?

rotund token
#

yeah

muted field
#

oh didnt realise he added job system

rotund token
#

he optimized his rvo simulation a good 1-2 years ago with jobs

#

i haven't looked at it recently

#

but i just remember it being a good example of how to use jobs with monobehaviours

#

it allowed him to take the simulation from a few k to 20k+

muted field
#

ah i see, i will look into it πŸ™‚ thanks

viral sonnet
#

I agree, it's the most sensible to not using MB code at all and instead passing MB data to jobs. Having said that, I'm not doing that. haha, got to rethink my approach in some cases because when every job is scheduled things that have never been a problem suddenly start being one. Like just reading and modifying some translation in a MB and writing back. Ooops, dependency problem, complete your job first. Annoying as hell

viral sonnet
#

that said, for some cases it's hard to decide. like, your whole rendering of actors is still gameobject based. Now you have an asset like KCC, why implement Rival that is for DOTS? If you want to make this work for others or be open to the eventual DOTS animation there's no real answer what's the best solution. In that case, some MB code is okay, especially when UI is involved too. Some JobHandle.Complete() are okay to support this.

#

so Rider is complaining about NotAccessedVariable and wants me to rewrite this to the bottom line in the screenshot. Is this actually a problem?

#

currently not in the mood to read assembler ^^

muted field
#

thing is regular threads allow data from classes

#

so what did unity do with jobs that meant we couldn't use them

viral sonnet
#

maybe I misunderstand the question but unity jobs are in unmanaged memory while regular threads work on C# heap memory

muted field
#

yeh but my point was why unity made it restricted to only unmanaged ?

#

the whole scheduling of jobs is nice and simple using unity's api where as i hate dealing with c# threads generally

#

would've been cool if they could support managed too even if we cant use burst just for the parallel work done

viral sonnet
#

managed memory is handled by C# and the GC which has LOTS of restrictions so Unity said, fk it, we just malloc in C++ code and give this memory block to C# users.

muted field
#

ah i see

#

i just like the ease of it setting up the threads for me without me needing to know how many my cpu can handle etc

#

more so than the the burst compiler which is a nice perk on top

viral sonnet
#

what I mean with restrictions is, C# can't even make sure that an array is linear memory. Newer versions of C# have stackalloc but I don't know if Mono has it as Mono is more like the bastard child of C# anyway. And yeah, I'm really not an expert on the C# memory front. I just believe they had very good reasons to just say screw it, we build our own.

muted field
#

isnt native containers linear memory ?

viral sonnet
#

it is

#

at the most basic form all native containers use C malloc

muted field
#

so we could just use them anyway even if not using jobs/entities

viral sonnet
#

yeah sure

muted field
#

as long as you remember to dispose i guess

#

wait do they dispose if you exit an application automatically

viral sonnet
#

problem is that native containers are quite slow on main thread C# code. I learned this the hard way

muted field
#

oh are they?

#

damn

viral sonnet
#

yeah, I don't know what all the reasons are but NativeContainers are only good in burst jobs IMO

muted field
#

i would've thought the linear memory would give them the performance boost

#

was this from your benchtesting or you read it some where?

viral sonnet
#

there's some form of marshalling from unmanaged to managed data as overhead which is the problem

#

I tested this extensively.

#

I mean it makes sense if you think about, the data has to go back and forth from unmanaged to managed memory

muted field
viral sonnet
#

native container data lives in unmanaged memory. now if you want to read unmanaged memory like an array element in C# land it has to go through marshalling. At least that's the way I understood it. Could be totall wrong lol but that's also how it works with C++ plugins so I think it's very much the same

#

to be perfectly honest, I don't know what's the big problem with unmanaged and managed memory. I would think it's all the same but it really isn't. For a more detailed answer you'd have to ask google

#

yeah seems all very technical and has to do with handling/controlling the garbage collector

#

the GC constantly scans and frees memory which just doesn't happen in unmanaged space

#

where it's all manually done

muted field
#

so if you malloc in unmanaged thats persistent until you free it

#

because GC wont touch it

viral sonnet
#

yes

#

I find the abstraction highly annoying. So much pain because of it πŸ˜„

#

but that's what you get for lazy devs who can't manage to free memory ...

#

I should not complain, C# is an amazing language otherwise

muted field
#

for me the problem was learning how to do it myself, when ever i asked about pointers on StackOverflow for C# all i ever got was replies of "don't do it"

#

or "why are you using structs when classes are references"

#

except unity does not let me use classes for jobs so i dont have a choice

#

so information is rather scarce when you touch unsafe

viral sonnet
#

yeah don't bother those are just opinions. kind of unusual for SO to have those answers

muted field
#

they didnt even answer they were just comments i never even got an answer for it πŸ˜„

viral sonnet
#

my primetime on SO was around 2008. seems it went downhill

muted field
#

every attempt i made my pointers were becoming invalid so im doing the mono pushing data to arrays method instead now

viral sonnet
#

huh, never checked that /// <summary> /// A "blank" Entity object that does not refer to an actual entity. /// </summary> public static Entity Null => new Entity(); Is it better to compare for Index == 0?

dense crypt
#

No, just do myEntity == Entity.Null

viral sonnet
#

i mean, it's a micro optimisation but doesn't the static involve a random jump in memory just to pull in index/version 0?

rotund token
#

last i checked you can have an entity with index 0, version 1

dense crypt
#

Also the implementation could change. So safer to not assume that index 0, version 0 is "null". Besides it's a lot more readable

rotund token
viral sonnet
#

huh, never checked how a static looks inlined

rotund token
#

what i mean is just that it'd just look the same if you did
myEntity == Entity.Null
vs
myEntity.Index == 0 && myEntity.Version == 0

dense crypt
#

Yeah this seems like a really easy thing for the compiler to detect and inline

rotund token
#

burst is also extra aggressive on this

viral sonnet
#

this is hard to read πŸ˜„

#

I certainly don't see any Index == 0 && Version == 0

#

looks like it reads from a static pointer

#

and goes through the normal compare method

rotund token
#

did you create a comparison?

viral sonnet
#

eh, I can't read that shit, can't even find the else. having tested the manual 0 comparison it looks like the same code

#

i can't find the cmp for the if, just the for loop

#

so I just say, all is good, burst is great and knows much better what it does than me πŸ˜„

muted field
#

iterating through monos to populate my native containers + the job + iterating to put the data back on the monos ended up being no faster than not even using jobs to start with and using regular managed stuff 😦

#

the job is fast but the other stuff undoes all the benefit

viral sonnet
#

then get rid of the monos πŸ™‚

#

uhm, is IJobEntityBatch patching referenced entities created by a commandbuffer in a comp or do I have to use commandBuffer.SetComponent to trigger the patching?

rotund token
#

only command buffer patches entities

viral sonnet
#

ok thanks

muted field
#

plus i dont think i even have it in 2021.3

viral sonnet
#

yeah, I see, then the only option is to reduce the overhead of creating and writing back as much as possible

#

keep the native containers but use an index in the MB instead so it knows where its data is

#

might be annoying though when GOs are created/destroyed :/

muted field
#

yeah hm i guess i could use events to notify each other

#

between the graph and the monos

viral sonnet
#

so 2021.3 is required?

muted field
#

well not really unless 2022 breaks everything πŸ˜„

#

99% of the project just uses regular mono stuff

#

since none of it is heavy work for me to need to optimise

#

its just the algorithms working on graph data that is the more expensive stuff

viral sonnet
#

Using Entities in 2020.3 would make a lot of things easier

white island
#

Ok so to make a tree structure in a value type way, a tree would be a node struct that takes other node struct a as values, right? Like this:

struct Node{
    public Node leftChild;
    public Node rightChild;
}
rotund token
#

usually you'd just store the index to the other nodes

#

otherwise you can't make changes to your nodes

white island
#

This is read only, constructed beforehand and put into memory as a native something or a blobbable whatever

#

But that is a good point.

#

And it might be easier to deal with traversing it that way but it might not matter

#

My use case is basically: after I make sure my AStar works I’m going to replace the super slow β€œget closest point” function with a K-d tree. And while I’m not on that stage yet I’m thinking about how to implement it now@

tawny geyser
#

have anyone used Etitas yet? Is it better than dots in its current state?

muted field
#

is there any particular reason why we shouldnt use unity's native containers over regular arrays etc for general code?

rustic rain
muted field
#

thought they were faster which was why they were beneficial for burst/ jobs ?

rustic rain
#

they are thread safe

#

also unmanaged

#

which lets them to be used with burst

#

but in normal code

#

you don't really want them

muted field
#

ah okay

#

do you mean slow in terms of doing for loops over them ?

#

or reading and writing to them too

rustic rain
#

slow in literally anything

muted field
#

ah

rustic rain
#

I remember some comparison made

#

year ago

muted field
#

so i should just convert say a Dictionary to NativeHashMap only when i need to use a job then

rustic rain
#

eeeh, Idk in this one

muted field
#

i see

muted field
#

seems its actually not that bad except in editor since safety checks are stripped in build or something

rustic rain
#

hmm

#

that's intersting to know

rustic rain
#

Is there a way to ensure my system gets Initiated in certain order?

#

I basically want to have Input class managed in ECS System

#

and in order for all events to work correctly

#

I need to ensure it gets created the last

#

so all other can subscribe

#

before events invoked

rotund token
#

i don't really understand why you need order

#

but to answer question not really

rustic rain
#
    public class InputController : SystemBase
    {
        private static PlayerControls _playerInput;
        public static PlayerControls PlayerInput => _playerInput;

        public static event Action<PlayerControls> OnInputInit;
        public static event Action<PlayerControls> OnInputDispose;


        protected override void OnCreate()
        {
            if (_playerInput != null)
            {
                OnInputDispose?.Invoke(_playerInput);
                _playerInput.Disable();
            }

            _playerInput = new PlayerControls();
            OnInputInit?.Invoke(_playerInput);
        }

        protected override void OnUpdate() { }
    }

It looks like this

#

if for some reason InputController will be created before other systems

#

they will not get Invoke

rotund token
#

ok

#

firstly i hate that you are even doing this πŸ˜„

rustic rain
#

why?

rotund token
#

a) event
b) static event
c) static fields

rustic rain
#

oh

#

static fields

#

rudimental

rotund token
#

but to answer your question

#

static fields mean you can't run multiple worlds in the same editor

#

if you ever want multiplayer shooting yourself in the foot

rustic rain
#

oh, I see what you mean

#

I guess

#

other way

#

is to do

#

World.GetOrCreate

#

instead of static

rotund token
#

i don't like systems ever accessing other systems but anyway off topic

#

to answer your actual question regardless if i think you should be doing it

#

not really

#

but just don't use events

#

wel

#

well

#

not exposed events*

rustic rain
#

why not?

rotund token
#

use a custom event subscriber