#Semi-rarely updated concurrent gameplay counters (achievements, stats etc)

1 messages · Page 1 of 1 (latest)

spare herald
#

does anyone have a nice burst-compatible pattern for global gameplay counters that can be updated "anywhere" fully in parallel but are generally updated relatively sparsely?

things like "damage done" etc that could be used for achievements and quests and stats and so on

the simplest solution that i'm thinking of is something like

public enum CounterId {
  DamageDone,
  GoldGained,
  EnemyKilled,
  ...
  // might have hundreds or even thousands of these,
  // potentially with some computed indices like "DamageDone + damageType"
}
static readonly SharedStatic<NativeArray<int>> counters;
public static void IncreaseCounter(CounterId id, int amount) {
  unsafe {
    var pdata = (int*)counters.Data.GetUnsafePtr()
    Interlocked.Add(ref pdata[(int)id], amount);
    // later: detect changes using a shadow array or something
    // like that in a system after completing all jobs
  }
}```

* the big benefit is that i don't have to specifically declare access to it in job structs etc, it can just be used "anywhere" in both bursted and non-bursted code, and in parallel jobs etc
* theoretically this is subject to false sharing though i don't expect to run into that in practice
* adding time- and state-based conditions ("do y damage within z seconds", "kill x enemies while airborne") will get pretty hairy
shy geyser
#

Create singleton with field NativeList<MyRequest>
inside of MyRequest feel free to pass data like Entity player or Id achievmentId and etc

#

And then from any system/job add those requests

#

inside of one specific system handle whatever this event supposed to do

#

This is for rare events though

#

which may involve complicated logic, but can be thrown out of anywhere

#

As for tracking per player stats, you may just use DynamicBuffer<TrackedStat>
where struct TrackedStat { public int statId; public double trackedValue; } and then just track whatever you want here, as long as system knows it's statId

#

Some kind of extension which would find existing stat or add new if doesn't exist would be nice for it

#

This probably won't be able to track literally everything, but for simple things it should be enough

spare herald
#

the problem with the singleton is that then i have to basically add it + a SystemAPI.GetSingleton call to a hundred different jobs in the project

shy geyser
#

As for achievements trackers themselves - the best would probably to make a system where achievements can be "constructed" via authoring,
This way you can create logic only once and then new achievements can be added by simply creating combinations of different variables.

For simple trackers enough would be to just specify StatId
For more complicated you may need to add additional achievment specific components, which enable/disable achievment tracking based on it's conditions

spare herald
#

like i'm expecting to sprinkle these counter increases in a whole lot of different jobs all over the place in different systems

shy geyser
#

you can make it parallel thread safe

#

via same locks ofc though

spare herald
#

yea it's more that i don't want to create a massive amount of additional boilerplate

#

i don't want to have to add new MyJob { ... counterRequestWriter = SystemAPI.GetSingleton<MyRequest>().buffer.AsParallelWriter() , ... } to a hundred different places

shy geyser
#

SystemAPI.GetSingleton<MyRequest>() is enough

spare herald
#

even so

#

a lot of this is going to happen in utility functions as well that will be called from multiple different jobs

shy geyser
#

well, if static context works for you - feel free

#

you can introduce all api via static calls this way

shy geyser