Viability of authoratative projectiles

Hey,
Currently have a system where the client fires a projectile with physics to the server and then the server replicates to all clients. I have removed functionality where the server made its own projectile as it was too laggy.

However, both bullets (client and server) were being simulated at 60 fps previously - an oversight I forgot about.

What if I simulated the server’s bullet at 20 fps and client’s at 60 fps, would this be viable with Roblox servers at a large scale (50+ players) or should I go stick to client hit detection and server validation? I think games like Arma use this method but I am unsure and I don’t know if Roblox servers are strong enough.

Thanks in advance.

2 Likes

It would probably make sense to both simulate the server at lower fps, AND put that projectile in a separate thread using Parallel luau. Simulate client projectile in parallel, too.

3 Likes

I haven’t thought of this before.
Though, at the same time, I have no idea how I would do it.

Would the parallell execution be done on the actual script stepping forward all the projectiles?

1 Like

Parallel execution has to be done in a separate script.
example for client:
client receives simulation request → client creates a new actor which contains the simulation script → clients sends the actor a message containing all required variables → actor calculates those variables and replies with result position and orientation → client updates the projectile with this result.
Once projectile disappears, actor is destroyed.
I know it sounds complicated, but once you get the hang of it, you’ll be able to optimize calculations like these in no time, and get up to 6x performance on client, and about 1.5-3x on server.

2 Likes

I’ve kind of just figured it just now, but I don’t instance new actors.
This is certainly very promising and I haven’t really dipped my hand into parallel execution.

I have a Flashcast instance thats required under an Actor, with FlashcastIndex basically just returning FlashcastModule.new()
image

Modifying Flashcast, I modify .step() into a ParallelSimulation (with flashcast.event being RunService.PostSimulation)

	function flashcast.stepBullet(self: Flashcast, bullet: Bullet, deltaTime: number)
		task.spawn(function()
			for _, callback in bullet.behavior._beforeStepCallbacks do
				callback(bullet, deltaTime)
			end
			
			bullet:move(bullet.position + bullet.direction * deltaTime)
			
			for _, callback in bullet.behavior._afterStepCallbacks do
				callback(bullet, deltaTime)
			end
		end)
	end

	function flashcast.step(self: Flashcast)
		for _, bullet in bullets do
			local deltaTime = os.clock() - bullet.lastTick

			if deltaTime < 1 / bullet.desiredFramerate then
				continue
			end

			bullet.lastTick = os.clock()
			flashcast:stepBullet(bullet, deltaTime)
		end
	end
...
	scheduler = flashcast.event:ConnectParallel(function()
		flashcast:step()
	end)

I have guaranteed that all the calls (beforestep and afterstep) are parallel safe, and then have a serial thread just read what information was put in the bullet:

	RunService.PreRender:Connect(function(deltaTime: number) 
		for _, bullet in FlashcastIndex.FlashcastInstance:getBullets() do
			task.spawn(function()

				Behaviours.UpdateBulletModel(bullet, deltaTime)

				if bullet.touched then
					Behaviours.DebugPos(bullet.data.hitPosition)
					Behaviours.CleanupBullet(bullet)
				end
				

				if bullet.data.shouldStop then
					Behaviours.CleanupBullet(bullet)
				end
			end)
		end
	end)

Is this sound? I don’t really understand what you mean by creating new actors. Are you proposing that everytime I want a bullet, I just copy the Actor instance then require the flashcast instance and then use that to create the bullet or something? Because right now I just have a shared Flashcast index under an actor to do that.

Should I instead be binding it to a certain RenderStep? Or should I keep using PostSimulation? New to this Parallel stuff, sorry.

I have never used the method you are using, but It’s most likely that the function is going to be cramped into a single actor. You want to avoid that.
Each actor represents a separate process, which can happen at any time within the frame. The reason why you want each separate projectile to be in it’s own actor is because each actor can only occupy the maximum of 1 thread, yet 1 thread can have multiple actors. By splitting them into separate processes, you’re allowing the Roblox scheduler to properly pack them, utilize the maximum amount of threads your environment has, and minimize time between each separate process.

So, what I’m suggesting is that each projectile you create has a separate actor bound to it, and every frame you iterate through those projectiles and send a message to their actors containing current properties, then get their reply (I personally use BindableEvents), and update projectile accordingly.

If you want to make sure that your processes are in proper parallel - you can add a debug profile to the function, and check Performance Stats. If all (or # of projectiles) your RBXWorkers have the debug profile, you did everything correctly.
I would recommend you first test with profiles if your current method works before changing it, as I’m genuinely not sure if that works or not.

the overhead of parallel lua is so high that it just ends up not being worth the effort and stuff for such minimal or negative performance gain, it only works well for extremely specific situations and I’m not sure bullets in this case is one of them. also I’m not sure if they changed it but parallel lua has had issues with allocating more cpu threads and Roblox has responded saying they aren’t satisfied with their dynamic thread allocation rn


I can’t tell. But it looks like only one worker thread is actually using it? (every green blob you see is a FlashcastParallel thread)

I think it switches worker threds every frame or something? I can’t tell. It doesn’t seem to be a straight vertical row of ‘FlashcastParallel’ threads.

I’m not sure what you’re referencing, could you provide a few examples of said overhead? I’m using parallel in my current project (a tower defense framework), and it did boost my performance by a lot, so if it’s a real issue, please let me know! The only overhead I’m getting is optimizing tables for actor messaging, and it only takes up like half a millisecond at most per frame, with performance boost of parallelization being about equal to the amount of threads.


Here’s what I mean. All the blocks labelled by white (and the ones unlabelled, but look the same) are the blocks that contain the Flashcast Parallel debug profile start and end. I wrapped the Flashcast:step() function around it.

Yup, that means your function runs in a single parallel thread. Try the method that I explained in my previous reply.
If you want to learn how to properly use MicroProfiler, follow guides here (docs) and here (Roblox Youtube)

you might have the specific situation that it works so well, I’ve benchmarked it in the past for chunk loading blocks (data decompression and stuff) and it only ended up giving a 5% improvement to performance despite roblox using 8 cpu threads, I’ve also tried using it for raycasting with similar results, it’s really dang hard to hit that sweet spot, you can search around and you will find is a common complaint

Oh, alright.

So I’m guessing here’s what I do:
I export the function to spawn bullets into a new module script under an actor. I then have a Flashcast Instance which, whenever you want to spawn a new bullet, copies the actor and the module script within and requires the module

Then the Flashcast instance (serial) tells each actor its registered to step using publishactormessage or something, and then each bullet under the Actor advances one step:

			bullet:move(bullet.position + bullet.direction * deltaTime)
		function bullet.move(self: Bullet, position: Vector3)
			local direction = position - bullet.position

			local raycastResult = flashcast.worldRoot:Raycast(bullet.position, direction, bullet.raycastParams)
			if raycastResult then
				table.insert(bullet.raycastResults, raycastResult)
			end

			bullet.position = position
			bullet.distanceTraveled += direction.Magnitude
			bullet.touched = raycastResult
		end

Am I doing this right?

Also, are there any great parallel resources you used, or did you just practice with it a lot?

I’m not sure about requiring the module and calling the function that way, it would probably make more sense to use Actor’s SendMessage and BindToMessageParallel methods with a regular script/localscript.

For resources I used, it was just the Actor reference, Multithreading/Parallel documentation which was already mentioned, and SharedTable / SharedTableRegistry reference. For videos, I find CrusherFire’s tutorial quite informative, and it explains both parallel and MicroProfiler.

I could instead then just use a local script or server script based on which run context I am then, I guess.

However, the rest of the logic should be sound, correct? Just that instead of the serial script interacting with the bullet directly it does so through parallel Actor messages.

Yup, you just offload the math to actors, and set up a way to communicate with those actors asynchronously. Other than that, your code should remain the same.

I’ll try this today and tommorrow and see how it works. Thank you so much for your help!
This is my first time really interacting with parallel LuaU as previously I never really understood how it worked. Thanks again!

1 Like

Sorry about this, but I also forgot to ask,

Given that I still need a serial thread to modify the cframe of the bullet to the ray, among other things which require modifying the datamodel, how would I access the bullet contained within the local scripts of each actor?

Would I use something like SharedTables? Or could the actors themselves publish something?
Sorry, I really have no idea

You can use task.synchronize() inside parallel code to make it switch to serial execution. Keep in mind that it should only be done briefly, preferably at the end of the calculation, as to not keep switching back and forth between parallel and serial. That’s the preferred method if you have to do something serial in between 2 large calculations. Otherwise, fire a BindableEvent from the actor, and then listen to it on a serial script.

1 Like

Oh ok, that’s useful I could just use BindableEvents.

I guess I could just pass the updated BulletTable (with the flags, speed, data, etc.) and then have the serial thread update their bullets table to that, then iterate over a table filled with all the bullets (indexed with each actor instance) and modify the data model from there.

I also assume when I’m done with the actor (such as when the bullet is considered dead by the serial thread) I can just do :Destroy() on the actor?

Thanks again.

1 Like