What happens when task.desychronise() is called without an Actor?

Recently, some design patterns I use revolving around task.descychronize() started throwing tons of warnings in console output. I started using task.desychronize() in place of task.wait() outside of Actor instances so support the creation of async lists which can effectively wait for 0 frames. And it works just fine, just take a look at this test for the module. Sorry for the arcane testing framework, I hope the example will still be clear:

image

The above test’s Be async pass fails without the calls to task.desynchronize() and task.synchronize(). The callback passed to async_list:get is called once we desychronize. So if this seems to be working… as unintended… but why is this considered a problem by the engine? It seems to work as if all execution is done under an Actor.

Further, why not just put the entire execution tree under an Actor to silence this warning? If that silences the warning without modifying behavior, why make people manually use an Actor instance at the top level when not doing it produces the same results?

I guess the real discussion at hand is if anyone has had any weird behavior using task.desynchronize in this context, or if I’m already experiencing the weird behavior and building around it. But the behavior I get seems pretty intuitive; I’m not sure why my code should be considered bad by the engine.

Yeah I don’t think task.desynchronize is really meant to be used in that manner. As far as I know it is still strictly meant for parallelization purposes. What you’re doing is definitely not how they intended it to be used, so let there be dragons.

According to the documentation, task.desynchronize will yield until the next parallel resumption cycle, which happens somewhere within the same frame. I believe this behavior can be replicated if you call the function via .Stepped and then yield with .Heartbeat.

Yes, I agree, that’s not the “right” way to use task.desync/sync.

However I get the same warnings when

  1. I have Modulescript, which is a class
  2. The Modulescript is located under ServerStorage/Actor
  3. The Modulescript is attached to an physical Instance in Workspace
  4. The Modulescript does something like
task.desynchronize()
print("I am desynchronized")
task.synchronize()

Why do I get this warning as 2) the ModuleScript in located under an Actor?

I can make it work w/o warnings by having the Actor in Workspace so that

WorkSpace =>
    Actor =>
        Instance
        Script (regular, not ModuleScript)

But this is a terrible way to manage larger games. Does anybody know what’s the right way to parallelllze a class module? Because class modules / OOP would really lend themselves well to parallellization because they are often self-contained.

That is actually the correct way to use actor scripts IIRC.

It gives you the error when you use it inside a ModuleScript because the final environment of the ModuleScript may not necessarily be in a parallelizable context. ModuleScripts are required by other scripts, and when that happens it will run under the jurisdiction of the former’s environment. Therefore, parenting a ModuleScript under an actor literally has no effect since it depends on who requires it.

The only solution would be to store a regular function inside the module and then only desynchronize it inside the actor script, something like this:

task.desynchronize()
module.DoStuff()
task.synchronize()

--or, using parallel signals:

event:ConnectParallel(function()
    module.DoStuff()
    task.synchronize()
    --do something else in serial
end)

This doesn’t agree with the testing I did a couple days ago. I was finally reorganizing my project to put the startup scripts under actors and then pull everything in from there (I already use a framework that pulls everything in with one script, so this was trivial). But it didn’t silence the errors, indeed only code which ran within the original script which is under an actor was allowed to use task.desynchronize. I put the primary modules which actually used task.desynchronize under just two actors and all warnings were satisfied.

But now there’s a dumber problem: Actors have to be scattered throughout the source tree as needed, making require paths look like game.ReplicatedStorage.Util.Actor.LazyModules.Actor.Signals which is just hostile towards rojo. It has deterred us from using actors once again since we have to structure our project around their existence too much to feel comfortable using them when their design does not seem finished.

A solution is not hard to conceptualize: If actors could instead be a child of the instance they are applied to, which i’m sure is possible barring any “but we don’t wanna work that hard / think that way” on the engine devs’ side, then the Actors could exist without ruining source trees. But given the pandora’s box method of updates roblox uses, this Actor thing will probably be a pain even 10 years from now. Sigh… I’m not sure how this made it to prod but it is pretty disappointing.

In fact, it’s very strange for actors to be applied to their children given highlights and many other effects for models are applied to their parents…

My mistake, I didn’t actually test anything myself and just inferenced the cause according to that other guy’s testimony. It’s dangerous to be using environment-specific functions within modules nonetheless as it opens up a door of ambiguity. You should only keep inside ModuleScripts what is guaranteed to run properly on ALL possible environments; otherwise, just transfer everything directly into the encompassing actor script.

Yes, I agree, a lot of people have talked about this. There needs to be a way to avoid tapping into the instance hierarchy just to multithread some calculations. It becomes a bottleneck in so many scenarios.

I guess their focus was on parallelizing the frontend aspects of your game. The Actor class inherits the Model class, which gives you an insight on what it is really designed for. This structure combined with the supposed granularity of actors means you’re supposed(?) to use it as a slot-in substitute to Models for grouping instances, so you can do things like parallelize individual cars and NPCs in the game. And because of this it is just inherently more hostile to backend pure-programming parallelization and really want you to use more instances :person_shrugging:

i have same warning, i just want to ask, is desync still works? or roblox just tells something

Sorry for the necro, but would the module’s values be updated for scripts outside of the actor’s environment after synchronize is called?

If not, I guess I’ll just have to deal with sending big tables over bindables and using a regular script for desync/sync.

A script that’s parented to an actor will always run in a VM, so requiring modules from that script will cause the module to load into the VM too. The environment is isolated and so no changes to the module will propagate into or out from the VM.

Consider using SharedTables instead, they are usually better for that purpose. There are plenty of resources on using it.