Performance worries: using .Heartbeat to run a function multiple times; contains a loop (for every individual vehicle)

I already have a detection of Seat.Occupant in code below that calls the vehicleCooldown() function when the player leaves the seat (which was not mentioned within this topic). However, with the code provided above I also wanted to somehow constantly monitor if the car has been pushed without anybody ever entering the seat, so it can also respawn in the same place that way also. I assume that there is currently no way to only have to fire the function when the car actually physically moves rather than doing checks constantly for multiple vehicles, to detect if one of them moves?

Moreover, If I am going to be using an infinite loop using task.wait() instead of .Heartbeat (bearing in mind that each vehicle would contain the same script), there would still be multiple loops running constantly in the game. Couldn’t that also cause a performance issue, similar to the use of .Heartbeat?

Even if I did try to control all the cooldowns for all the vehicles in one place which was just an idea, it would most likely create its own set of challenges and make things more difficult.

Well, the documentation for GetPropertyChangedSignal suggested the use of physics-based event to detect changes in position. In that case, you could put a region block and connect the event Touched or TouchEnded to detect when the car moves to or away from a position.

The documentation page Computation mentions that breaking up expensive tasks using task.wait() can spread work across multiple frames to reduce the impact on frame rates.
Even if you have many loops running, the use of task.wait() ensures that they are running at a limited frequency.

I believe :GetPropertyChangedSignal doesn’t work in my use case since:

The event returned by this method does not fire for physics-related changes, such as when the CFrame, AssemblyLinearVelocity, AssemblyAngularVelocity, Position, or Orientation properties of a BasePart change due to gravity.

I tried it without using code to move the model and instead moved it physically when I was testing it but that didn’t work, which brings me back to my previous point:

Correct me if I am wrong though. If only I could simply do it this way instead of constantly having to check the model’s position, that would have pretty much solved my topic here.

I know that GetPropertyChangedSignal doesn’t fire for physics-related changes as shown in the Limitations section:

Thus, I suggested using Touched or TouchEnded to detect when the car model leaves a region.

That could be a possibility. However, would a better way of using .Heartbeat or any other loop on multiple vehicle models, would be to fire a BindableEvent and have that listened to in every vehicle model, so your constantly checking when each car has moved without using .Heartbeat for every vehicle separately.

Also, is .Stepped a better choice than using .Heartbeat in my use case, or is there not much of a difference? In addition, I’ve heard something about using Parallel Luau but I am not sure if this is particularly related or would be particularly useful in my use case, or whether this would just over-complicate things further. Otherwise, by what you suggested regarding using a .Touch event for region blocks where the car would respawn if it touches these blocks, would probably be the best option here.

I believe that there’s little difference in performance in connecting functions to .Heartbeat or to a BindableEvent connected to .Heartbeat.
Also, you could try out CollectionService (or the forum about it) to handle vehicle respawns inside a single script.

Since the vehicle respawn function has a loop check with task.wait(), using .Stepped or .Heartbeat shouldn’t matter since the task.wait() yields then resumes the thread on the next Heartbeat step.

According to Computation again, Parallel Luau (multithreading) can be used if you for expensive tasks that doesn’t access the data model. You could use it by parenting the script to an Actor and calling task.desynchronize() for calculations like Position / Vector distance or cooldown, then task.synchronize() for respawning the car.
However, using multithreading for just a small number of computations could be over-complicating things. You also don’t want to use multithreading for long computations, as mentioned in Best Practices.

1 Like

What would that look like if I tried to implement it into the script instead of using .Heartbeat, or how would I fire the vehicleCooldown() function using CollectionService?

This is the jeepRespawn() function by the way:

local newJeep = originalJeep:Clone()
newJeep:SetPrimaryPartCFrame(originalCFrame)

for _, passenger in ipairs(Seat:GetChildren()) do
	if passenger:IsA("Model") then
		passenger:Destroy()
	end
end

Jeep:Destroy()
newJeep.Parent = workspace
print("Jeep regenerated in the same place!")

If you use CollectionService, you would probably add a tag to each car model, then use something like this:

-- Collection Service functions
local function onInstanceAdded(car : Model)
	-- Validate car
	-- Set up function
end

local function onInstanceRemoved(car : Model)
	-- Validate car
	-- Clean up stored connections
end

local function setUpCars()
	task.defer(function()
		for _, object in CollectionService:GetTagged(tag) do
			onInstanceAdded(object)
		end
	end)
	CollectionService:GetInstanceAddedSignal(tag):Connect(onInstanceAdded)
	CollectionService:GetInstanceRemovedSignal(tag):Connect(onInstanceRemoved)
end

-- Calling functions
setUpCars()

You would probably have to use local scopes, tables, or modules to store the cooldown information for each car.
The .Heartbeat connection can be stored for each car & cleaned up in the instance removed function, or it can be a single function that calls vehicleRespawn() for every car.
The implementation will be slightly complicated, but it is better than creating a script for each individual car.

So I would put all the cars together inside a Folder or something, place the script you provided inside ServerScriptService or the Folder and do it like that?

Also, where you are setting up a function that makes sense but removing a function; how does that work?:

local function onInstanceRemoved(car : Model)
	-- Validate car
	-- Clean up stored connections
end

Finally, you have shown me what the separate script would look like but how would I connect that to each script inside each vehicle model? Would I still keep .Heartbeat or how would that work?

You can put the script inside ServerScriptService, but the car models can be in different folders since they are obtained through tags and CollectionService.

onInstanceRemoved() fires when a tag is removed from an instance. This is often used for cleaning up resources to prevent memory leaks.
The code sample for GetInstanceRemovedSignal shows an example of cleaning up connections when a tag is removed.

I’ll provide an example for what you can do:

  • First, define vehicleCooldown(car) and respawnCar(car) such that they respawn the car specified in the parameter.
  • Next, in onInstanceAdded(car), create and store the connections to vehicleCooldown(car), like how the the code sample in GetInstanceAddedSignal did with .Touched.
    The connections can be:
    • .Heartbeat (in general),
    • .Touched (for region blocks),
    • :GetPropertyChangedSignal("Occupant") (for seats).

This way, you don’t need a separate script inside each vehicle model (except for Data Sharing module scripts).

Therefore:

Do you think using a ModuleScript, where you’d require the ModuleScript from each script inside each vehicle model, would be a more effective approach than compared with using CollectionService, since this sounds more complicated, even though it seems like it might achieve pretty much the same result, which is to reduce the duplication of code in each vehicle model separately?

I think it’s more effective to use a single script to set up the vehicles. CollectionService is a way of retrieving all vehicle in the DataModel in a single script.
The functionality of the ModuleScript in my post was for extra configuration of the vehicle (ex: different cooldown seconds), not for storing general functions like respawnCar().

I meant just using a ModuleScript in general and having it being required in each vehicle model, rather than using CollectionService, entirely. Would there be any advantage of doing this or is there a more rewarding advantage of going down the CollectionService route, even though it could potentially become more complicated whilst achieving similar results, or not?:

I would avoid placing scripts containing functions in each models because it would be harder to manage than if we used CollectionService.
(see the post on CollectionService, section 2. Using CollectionService)

I’ve developed with JToH Tower Creation Kit, which currently uses a ModuleScript for each type of scripted obby mechanism.
When updating the kit, it is quite difficult to convert all of these mechanisms into the newest version. Through CollectionService, you will only need to update a single script to convert the mechanisms.

Yeah I meant the entire script placed in each vehicle model; sorry, I did not make it clear enough. The code I provided in this topic only includes the function connected to the .Heartbeat, but I was thinking of placing the entire script inside a ModuleScript, (including the .Heartbeat connected to the vehicleCooldown() function), and just requiring it in every vehicle model that needs it, instead of doing them separately which could cause greater performance issues, because I would be independently using .Heartbeat in each vehicle script, or is that not how this would work?:

The performance of using .Heartbeat for separate vehicle scripts won’t differ much from using .Heartbeat for each vehicle in a single script.
I only suggested CollectionService because it avoids the use of identical scripts that could be hard to manage in the future.
You could read more about the advantages of using CollectionService in the links attached to the previous posts.

That is what would happen also, if you required the ModuleScript containing the same code, and required it in every script that’s inside every vehicle model and not using CollectionService entirely or; since this seems to be more complex when you could just do it that way, unless there is an advantage of using CollectionService instead?:

1 Like

I can see your point.

Overall, it could just be a preference, but there’s a slight advantage with using CollectionService.

Here are the two methods that we mentioned:

  1. With Scripts in each vehicle model requiring a single ModuleScript, the DataModel will contain multiple Scripts inside Models and a single ModuleScript inside ServerStorage or ReplicatedStorage.

  2. With CollectionService, you would put a single Script in ServerScriptService or StartPlayerScripts, and perhaps an additional ModuleScript to organize the functions.

Comparing the two methods, you’ll see that:

  • The first method requires more Scripts than the second method.
    This creates more Instances, which could be difficult and messy to work with.
  • Since the Scripts in the first method are placed in Models, they will only run on the server side of the client-server model once the model is parented to Workspace. You can’t run client-sided scripts since they only run in certain containers.
    The Script used in the second method can be ran from both the server and client side of the client-server model, allowing for client-sided scripting.
    In the case of respawning vehicles, you won’t need to do client-sided scripting.

Overall, using CollectionService reduces the number of Script instances (which improves manageability and possibly performance), and it allows similar objects to function both on the server and the client side of the client-server model.

Also, using CollectionService wouldn’t make things more complex. You could let the examples in the documentation for CollectionService guide you through ways to use the service.

1 Like

I am only accessing the server side as you know. Therefore, there is less of a reason to use CollectionService and rather to use a single ModuleScript instead? In theory if I am using a ModuleScript and requiring it in every script inside every vehicle model, I’d only be using one script of which I could edit from instead of having separate scripts?

However, I assume that there must be some advantage to using either of these because there must be some performance improvements, if your using the same code all in one place?

Yes, you would only be editing one script, but there’s just the tiny issue of having extra, identical Script instances that require the ModuleScript.
Also, with CollectionService, you could do :AddTag() or :RemoveTag() to dynamically add or remove a vehicle, which is a feature that the ModuleScript method can’t provide easily.

As mentioned in ModuleScripts: “Having multiple copies of a function is disastrous when you need to change that behavior.”
The improvement in manageability is already an advantage, even though there’s little impact in performance.