Low-level NPP questions

Hello! We have been exploring building our own gameplay systems on top of the Network Prediction Plugin. I’ve found it has a lot of potential for building networked gameplay and I’m excited to see it reach production readiness.

I have a few small questions that have come up over the course of development.

  • FNetSimTimeStep uses an int32 to represent the time step miliseconds. As a result, frame times are truncated, e.g. a 60 ticks per second simulation has its delta time truncated from 16.666ms to 16ms. I found this to be a particular problem when writing some code inside the network simulation that needed to deal with animation notifies, as the animation playback according to the World delta time would quickly drift from one using the simulation time step. Is there a better way to work around this, or any plans to change how the sim time step is represented?
  • In developing our system, I wanted to serialize more complex types into the sync/aux state, in particular FInstancedStructs and TObjectPtrs. A barrier to this is that the NetSerialize methods that must be written for NPP don’t receive a UPackageMap pointer, so class/object pointers cannot be resolved. I could hack around this by casting the passed FArchive to its underlying type and extracting the PackageMap from there, but I wonder if this is an intentional limitation or an oversight that could be changed in future?
  • Often we want to expose delegates from the network simulation to external systems for certain instantaneous events (for example, health has reached zero). In choosing where broadcast these delegates, we ran into questions.
    • Sending events from SimulationTick means they cannot be handled on simulated proxies. Also on the autonomous proxy, the event may not be fired if the relevant trigger for it wasn’t predicted locally (e.g. the player took damage only on the server and the state of the health value on the client was stomped following a RestoreFrame, meaning there was never a SimulationTick in which it “became zero”).
    • Sending events from FinalizeFrame allows them to happen on all net roles. However, because FinalizeFrame happens once per World tick rather than per simulation tick, certain events may be lost if more than one simulation tick happens per frame (because we can only compare the sync state at the end of this frame with the one from the last FinalizeFrame, if multiple simulation ticks happened in between we might not be aware of any intermediate state that should have triggered the event).
    • Sending events from an NPP cue with the Strong trait allows them to be predicted, and replicated on simulated proxies. However, we weren’t sure if this was in line with the intended usage of cues, since the handlers of our events might not be purely cosmetic and might in turn modify simulation state. We didn’t try using this approach so far.
    • Did you have any thoughts about the best practice for sending events to external systems or to other network simulations?

Thanks!

RE: FNetSimTimeStep

The reason for using integer milliseconds is to make deterministic simulation possible, without floating point precision getting in the way. This is primarily for fixed tick mode. The truncated time isn’t dropped, however. It is left in the float-point time accumulator for the next tick, and there shouldn’t be a strong divergence from the world clock but there will be a fluctuating offset between them. 16 ms doesn’t divide 1000 ms into 60 evenly, but over time, you’ll average 60 simulation ticks per second (sometimes 59, 60, or 61).

It is always going to be difficult to sync something outside of the simulation with something inside. You may consider driving your animation tick from the simulation time. In some cases, we’ve sampled the state of anim notifies independently of where the actual visual animation state is.

RE: NetSerialize

We have been serializing pointers to net-addressable objects simply using the FArchive (e.g. “Ar << MyAddressableObject”). Is there a particular case that doesn’t work?

RE: events and cues

The comments at the top of NetworkPredictionCues.h contain some recommendations here. Cues were intended to be for non-simulation-affecting things. They are not guaranteed to replicate, so they may be problematic under less-than-ideal network conditions. We are also grappling with this with our work in async networked physics movement, and are looking at a message/event queue system to broadcast simulation events on the game thread after simulation steps.

Hope this helps!

which can hold Blueprint structs using FInstancedStruct

By Blueprint structs, do you mean User-defined structs? That’s something we’ll be looking at in the very near future in support of an internal projects. I’ll note the FInstancedStruct / PkgMap case as something for engineering to consider.

Thanks a lot for the answers!

RE: FNetSimTimeStep

I had missed that specific logic for accumulating the extra unspent time, it’s good to know that is being considered. I agree it’s a challenging problem to sync animation and simulation in general, possibly the solution in our case is to allow that sync to be “looser”. We already implemented simulation code to sample the notifies independent of the “actual” montage state, so perhaps we should be less strict about the timing matching up between the simulation and actual montage.

RE: NetSerialize

To be more concrete on what I was trying to do, I wanted to explore the possibility of implementing simulation state in Blueprint, so I wrote a structure similar to FMoverDataCollection, but which can hold Blueprint structs using FInstancedStruct. This ends up calling FInstancedStruct::NetSerializeScriptStructDelegate which will have to serialize by iterating the struct properties, and relies on UPackageMap. Perhaps there is a better way to achieve this? I think I was mistaken in my original post in mentioning TObjectPtrs being problematic as well. As you said, they would net serialize fine if added to a struct with a native NetSerialize rather than using my custom data collection.

RE: events and cues

Thanks for pointing out that comment, those discussions are similar to the thought process we were going through. I will be interested to see the implementation for async movement!

Thank you again for the help :slight_smile:

Yes, both structs defined in Blueprint and regular USTRUCTs, without having to implement a native NetSerialize/interpolate/reconcile method on them.