Is it possible to decouple Update() from main loop and trigger multiple times per frame?

I have time-ordered data I want to “put through” my Update() loop…

  • In my project there are moving GameObjects and their Transform and velocity are stored in a time-series database (asynchronously to the game play)

  • As these objects move around the Scene environment they trigger actions by GameObjects in the scene

  • Example:

  • When an object enters a collider mesh, the collider mesh’s “TotalObjectsThatHaveEntered” is incremented +1

I am working on a “know the past” functionality where I would like to apply a time window of data to all MonoBehaviors in the scene.

Is there a way I could trigger the Update() method on all GameObjects active in the scene multiple times before the next frame is rendered?

  • I would update GameObjects in the scene using historical data from the time-series database and trigger the Update() method as many times as the time window
  • I would then pull the resulting value such as “TotalObjectsThatHaveEntered” from the GameObjects in the scene
  • I would show a “calculating…” screen while the time window is processed

This way you could apply old GameObject data to an Active Scene for “analytics” purposes.

I am not fully sure what you mean, but if you don’t want it to be rendered each frame, don’t use update.
An alternative might be a coroutine.

Start with settings a loading screen
Use a loop with some functionality
Use yield return null if you wanna go to the next frame
Etc

For asynchronous stuff, using tasks also can be great

Appreciate the response! Unfortunately that doesn’t perform the necessary operations

There is a “live game” running where GameObjects in the scene are interacting (like any game). I would like to take the current scene GameObjects (whose MonoBehaviours run on ever Update()) and then have them interact with data I run through the system (old game state). So I would like to trigger their Update() methods using “injected” game state data.

Again, use custom functions for that and just change the data it uses in Update(). Really don’t see the big issue, sorry

If you want your code to run multiple times in one update, just use a loop. Put the “data you run through the system” in a queue and then, when your update runs, keep popping the queue in a loop until the queue is empty.

Sorry I must be misunderstanding you.

When a user hits the “query” button, I would like to run the Update() method of all GameObjects active in the scene. And update the GameObjects between each trigger of "Update().

I am not sure how your proposal accomplishes that and I do not see any details of how to get that done.

If anyone could help with this problem it would be hugely helpful!

This is very interesting.

So I would add a “queue” to every Update() to objects that I would want to be part of this system?

And every GameObjects Update() does a check to see if any data has been loaded in the queue that must be processed, otherwise continue as normal?

This would require that I “remember” to add this “check queue” functionality to every Update() method. Do you think there is a way of doing this where I wouldn’t have to depend on developers to remember to include that “check queue” functionality?

Does every object need it’s own queue, or could you just use one queue for everything? You could always have a manager object that keeps an array or dictionary of all the other objects, processes the queue and updates the objects that need it.

I’m still not entirely sure why would you do this step by step if you don’t render the in-between results. Why don’t you calculate all the stuff in a loop and apply the results in the one frame?

1 Like

Interesting, do you mind elaborating a bit further given the following use case (really appreciate the help!):

  • I have “Human” GameObjects traveling through out the scene, they have an x,y,z position

  • I save the position of all “Human” GameObjects every frame

  • I have GameObjects that respond to “Human” GameObject data

  • HeatMap GameObject: every update it adds a +1 to the grid tile that a “Human” GameObject is above (which creates a live heatmap)

  • SpaceOccupancy GameObject: if a “Human” GameObject enters the SpaceOccupancy GameObject, the SpaceOccupancy GameObject gets +1

  • A user can make an arbitrary amount of these GameObjects ^ in the scene

I want to be able to hit a “query mode” where I can make all the active GameObjects in the scene run a time window of historical data (example 12/18/21 12am - 12/19/21 12am).

If every Update() function for all scripts attached to all GameObjects active in the scene has a:

  • Check if queue has data, if it does, process it first, otherwise continue

I can update the queue with data and have it be processed, but this would require that every script that could be attached to a GameObject in the scene has the “check if queue has data” functionality. Is there a way that this can be done without having to add it to each Update()?

Thank you for your response!

If there are 12 GameObjects in the scene that need to act on the historical “Human” GameObject data, what is the “loop” you are referring to?

I don’t refer to the loop, I was referring to a loop (what you would be writing to catch up with the data for your GOs).

Okay, just for the sake of clarity:
If you don’t render the in-between results (and you said, you don’t), then:

  • the only transform.position matters is the last one (no one cares where the object were in between)
  • the only velocity matters is the last one (no one cares how many times slowed down or speed up in-between)
  • the only things you need to loop for are like counters: how many times the object entered a collider, etc

For this, running the entire engine’s update loop is a bit of a waste. So unless you have something (which you didn’t mention up until now) that requires engine calculation and cannot be saved as data in your “history” database and cannot be calculated, interpolated out of them, you are better off running the calculations on your data manually and render the end results in only one frame.

ps: if you tell us your end-goal, we may be able to help you better (I mean the real goal, running this “simulation” is just a mean to an end, so what’s that end?)

1 Like

Ah sorry I see that I have not explained that the user can create new objects and then run old data on it.

So in the case of a “HeatMap”. Every time a “Human” GameObject is on a tile of the grid, +1 must be added. So hopefully that explains why “the only transform.position matters is the last one” is not a correct assumption for this use case.

I believe this comment might add the color you are looking for? (let me know and if not I will add): Is it possible to decouple Update() from main loop and trigger multiple times per frame?

The user creates GameObjects that run a calculation on presen “Human” GameObjects. These new objects that run calculations might not have existed in the past. So we want to “run” old data as if the GameObject was there in the past.

If you are using physics, you might be interested in this method:

It looks like it allows you to iterate the physics step multiple times per frame.

Nice!

Does this work on non-physics functions? Like an Update() method adding a +1?

No. It’s useful for physics because you don’t have direct control over the physics system. For something simple like adding +1, it’s really unnecessary, since you can just write code that does that.

Could it be that the topic of this thread is simply about replays and ghosts (unity replays and ghosts - Google Search)?

I will look into this thank yoU!

After looking into this, this unfortunately does not fit the use case. In ghosts and replays, the actual game is replayed in real-time. What I mean is that the actual “ghost” moves per frame the distance it was at each frame.

I would like to, within 1 frame, move a ghost through all the positions it was, and check for any interactions with elements.

To extend the analogy: imagine a replay where instead of watching the ghost race the entire race again (which would take as long as the race took), instead I put “boxes” on the race track and then ask “how many times did the ghost enter this box in that race?”.

I hit the “ask” button, I wait a bit, and it responds “12” (or whatever the number was).

Such stats are implemented using a counter and do not require waiting a bit.