Full Release of Parallel Luau V1

So once each serial/parallel phase is done, scheduler will switch to another if there is desync/sync queued until it’s all done and move on to the next frame right?

Also, would frames begin with serial phase if that is the case?

And… Even though there’s no use, will script return to serial by itself if it’s still in parallel with no task.sync? Just realized it wouldn’t be of any use to leave the code on parallel

Ah, so a scheduler would be the best solution? (if you had to do it from parallel.)
It essentially should do the same stuff but allow you to have much more flexibility over what you’re doing?

Snippet of code
-- Called in Parallel
	function ParallelSchedulerService.Job(JobType, ...)
		local JobId = InfinityECS:_Id()

		Awaiting[JobId] = {}
		Jobs[JobId] = { JobType, { ... } }

		if JobType == EnumService.JobType.Namecall then
			local JobArgs = { ... }

			Jobs[JobId][2] = {
				table.remove(JobArgs, 1),
				table.remove(JobArgs, 1),
				JobArgs
			}
		end

		return JobId
	end

	-- Ran in non-parallel
	RunService.Stepped:Connect(function()
		for Id, Job in Jobs do
			local JobResult
			local JobType = Job[1]
			local JobData = Job[2]

			if JobType == EnumService.JobType.Routine then
				task.spawn(table.remove(JobData[1]), table.unpack(JobData))
			elseif JobType == EnumService.JobType.Namecall then
				JobResult = JobData[1][JobData[2]](JobData[1], table.unpack(JobData[3]))
			elseif JobType == EnumService.JobType.Property then
				JobData[1][JobData[2]] = JobData[3]
			end

			for _, JobCoro in Awaiting[Id] do
				coroutine.resume(JobCoro, JobResult)
			end

			Jobs[Id] = nil
			Awaiting[Id] = nil
		end
	end)
1 Like

Interesting, could you link me to Elttob’s original code?
Cheers!

Great addition to game optimisation, but for now it’s not so useful because we haven’t shared storage.
Any news about when shared storage will be added?

2 Likes

Do you need actors to parallelize your code? It seems as if that’s the only way to parallelize as of now. In the future, can we parallelize within the same code without multiple actors? How about a shared VM?

2 Likes

Any update to this? @zeuxcg
A lot of us desperately want to use parallel luau but can’t because our entire codebases are a bunch of modulescripts called by a single source script.

2 Likes

It’s on the shortlist.

No - you can not exchange functions. This goes against the memory safety / threading limitations - parallel code must not have direct access to any Luau objects without special interop. The shared storage we’re looking into is an object like an Instance that allows key/value pairs like Attributes, but it’s not a table.

No shared VM, at least not for many years - VM separation is absolutely critical to parallelization in our case.

You must use multiple starting scripts. You don’t have to necessarily redesign your code base, eg helper libraries like this one (note, we plan to audit this code to make sure it’s maximally efficient but haven’t done so yet) can hide some of the management but the VM separation is absolutely critical.

2 Likes

Since the latest or prior the update prior to the latest, my games code using RenderStepped:ConnectParallel has been crashing 100% of the time, it however works with RenderStepped:Connect.

I have been unable to properly find the cause since it purely crashes ingame and in studio, and the code worked fine until the 24th (aproximate).

Im sorry if this isnt the right place to post this; also if required I can share the code and reproduction steps in dms or wherever.

Please file this as a bug via Bug Reports with the reproduction place attached.

1 Like

You must use multiple starting scripts. You don’t have to necessarily redesign your code base, eg helper libraries like this one (note, we plan to audit this code to make sure it’s maximally efficient but haven’t done so yet) can hide some of the management but the VM separation is absolutely critical.

I’m not suggesting that we should have shared VMs, I’m simply talking from the perspective of making this feature intuitive to use without changing how it fundamentally works on the backend. If we could spawn new actors (separate VM) within a script it would make parallel Luau infinitely more appealing.

Something like:

This would allow for parallelism to be used with minimal changes required to how you structure your code. Having to separate chunks of code from its place of origin and slap it into its own script object and then communicate with it using bindableevents feels really hacky and unappealing.

I would just make my own wrapper for the actors system that does what I’ve described, but there’s just simply no clean way to do it on runtime. You can’t create a new script on the fly and set its source, you’d need plugin security for that. So you’d need to have a script you’ve already created in advance, sitting in its own actor, with a little bit of code in it ready to call loadstring with the string of code you’ve passed in through the bindable event. And then if you want to have more than one actor going at once, now you need to have multiple of this actor script setup copy and pasted in some container somewhere, all ready to go. And then you’d need to have a little system to delegate tasks among the available actors.

And after all that, you still can’t get around the fact that the code you’re passing through to your system has to be written in string form, which SUCKS cause now all your editor’s syntax highlighting is useless and the code doesn’t want to be tabbed cause it’s a string, not a block of code. Maybe I’m just not familiar enough with the little tricks in lua and maybe there’s a secret way to get around this, but the point being, I shouldn’t have to rely on someone’s obscure (potentially bloated) library just to do something basic and intuitive

Actually, parallel worker is almost exactly what you are wanting :smiley: Heres the link to the repo if you still haven’t take a look at it https://github.com/MaximumADHD/Roblox-Parallel-Worker
(zeuxcg posted the wally package link, which if you are using wally its very helpful :D)

Usage:

-- Module
local taskModule = {}

function taskModule.Execute(self: any, addingNumber: number): ()
        -- some task
        local addedNumber: number = 0
        for _ = 1, 10000000000000 do
                addedNumber += addingNumber
        end
        self.StringResult = "done executing in parallel"
        self.AddResult = addedNumber
end

-- this is for routine task
function taskModule.Update(self: any, dt: number): ()
        -- define some routine task.
        
        self:Finish() -- call this to disconnect
end

-- return true if you want to stop the Update() from being connected again i think? runs before / after Update(). Statefully disconnect Update, instead of calling self:Finish from inside Update.
function taskModule.IsFinished(): boolean
        if someCondition then
                return true
        end

        return false
end

function taskModule.GetResults(self: any): (string, number)
       return self.StringResult, self.AddResult
end

return taskModule

-- Other script 
local ParallelWorker = require(path.to.parallelworker)

local newWoker = ParallelWorker.new(path.to.taskModule, 12) -- how many actors to allocate

local dispatched = newWorker:Dispatch(5) -- runs it
dispatched:Cancel() -- cancels it

local wasFinished: boolean, message: string, additionResult: number = newWorker:Invoke(2) -- runs it, then waits for the result
print(wasFinished, message, additionResult)

(Lots of edits cause i kept messing up with the example code lol)

3 Likes

Okay, that’s actually pretty nice. The source is extremely clean and not bloated at all.

But, I do see that the library requires you to pre-segment code into a “task module” and feed that modulescript into the library to run parallel code. It’s definitely the nicest way to work with what’s available to us with parallel luau and actors, but still not ideal.

I still think being able to spawn a “task” from within code is a very important quality of life change that would make parallel luau feel infinitely less hacky. And so, MaximumADHD’s library unfortunately does not grant that ability.

2 Likes

I dont have the permissions to file a bug report, would It be okay If I were to file a bug report but dm it to you instead?

We have a task on the roadmap to look into this further, but it’s increasingly likely that the interface that we’d be able to provide from the engine would be very similar to a library like the one mentioned above. So at this point it’s unclear if we should - we might! but it might be better to rely on community libraries for this.

We’ve looked into a possibility of creating an actor from a function, like you suggest, before - the core issue is that there’s no way for that function to cleanly refer to locals declared outside of the function; syntactically, the function body might be in the module, but we’d need to cut off all connections to the module that actually spawned the work. For now we concluded that this semantics is just too weird, especially together with current restriction on inability to require modules from parallel phase it makes it not very useful for how intricate the internal mechanism would be.

5 Likes

Please send it to @Bug-Support

2 Likes

@zeuxcg I think Parenting scripts under an Actor is a nuisance, especially games that use Rojo with ModuleScripts with one Script and LocalScript

Would be much better to have a BaseScript.Actor Property or BaseScript:SetActor instead of having to Parent them

Should I make a feature request for this?

6 Likes

If your question is ever “I want something changed, should I open a feature request?” the answer is probably yes.

Though I’d argue the common God Script layout for games is a bit lackluster anyway and it’s not taking advantage of Roblox’s environment properly to only have one script in your entire game.

1 Like

I’m pretty new to this feature, and it seems to me like this video does a pretty good job of explaining the difference between regular code and parallel code.

I was excited to use it because I have three separate scripts which have their own RenderStepped connections, doing things like rotating coins and other map parts that are on the screen. I switched from messing with their CFrame properties to workspace::BulkMoveTo after I saw it cut down like 30% on the Script Usage percentage thing.

Then I saw this:

I don’t really know what this means. Why is it unsafe? What does unsafe even mean? Are there plans to change it? Is it better to stick to the old single threaded method with BulkMoveTo or switch back to regular CFrame changes but with this parallel scripting thing?

Apologies if this post sounds naïve about the whole concept behind this feature. I didn’t even know how to use the microprofiler before today, haha

1 Like

There are some inherent limitations with multithreaded code, functions that modify the global environment (e.g. workspace) is one of them. Basically, when you have two independent threads trying to modify the same value, you can’t really be sure what will happen; who will run first, who will get overridden, what the value is actually going to be, etc. (this is called race condition). This inconsistency is what makes certain functions “unsafe” to be used in parallel code due to their unpredictable nature.

This is why there’s also the task.synchronize() function that will join back the parallelized threads. You’re essentially getting the threads to do a “handshake” and form an agreement on what is to be done by putting them back in chronological order. After parallelizing, you must re-synchronize the thread first before modifying the global environment again (albeit there are some exceptions)

2 Likes

I’m somewhat disappointed now that this is fully released. I know thread safety is important, but not being able to access common memory from different threads during sync operations makes this feature completely useless to me. Most use cases I have revolve around distributing large data operations over multiple threads, but this also means large outputs. So if someone knows a magical way to performantly send a 500x500 weight map through a BindableEvent (the total result usually contains 5-10 of them), please tell me as I’m starting to question whether I should keep using Roblox at all.

2 Likes