Update 0.4.0 Download
- API rework: removed all old zone registration methods and all string key usage. New API forces the user to register zones and track entities in correct order. For example, with the new API you cannot try to observe an entity before it’s registered or before a group is registered
- Added Zone, Group, and Tracked objects
- Zone and Group objects can be instantiated with .new while Tracked objects are returned when calling :track
- Zone specific methods (anything that used to require a zone name) moved onto new Zone object
- Method name changes: isPointInZone → isPointInside, getRandomPointInZone → getRandomPointInside, updateZone → update, removeZone → destroy, entered → onEnter, exited → onExit, changed → onZoneChange, addZone → ZoneService.new, addZoneFromPart → ZoneService.fromPart
- getSubjectsInZone method removed
- Subject type changed to “Entity” and all subject variables renamed to entity for more clarity
- Added metadata and destroyed flag to zones
- New group creation method via Group.new
- Add/remove zones to/from groups using new :add and :remove methods
- Destroy groups using Group:destroy
- If a zone is not added to any group, it defaults to a default internal group. In this state, zones cannot be observed with :zoneChanged
- A single zone can now belong to multiple groups, which scales better than creating multiple copies of the same zone under different groups. That said, scaling with multiple groups is still not amazing due to an information limit, so it’s recommended the user does not add the same zone to multiple groups unless absolutely necessary
- Max groups limit removed
- Reworked group tracking logic: scalability with number of groups improved significantly
- Removed unnecessary internal bookkeeping
- changed signal (now called onZoneChange) moved onto Tracked handle
- getZones method moved onto Tracked handle
- Added adaptive polling algorithm: dynamically selects the optimal frame interval, improves performance significantly in some cases
- Polling interval is no longer configurable; instead, there is a new polling rate config
- Added strict typing for new type solver
New Benchmarks
Polling rate = 30Hz on both, unlimited frame budget on QuickZone. Same benchmark code as before (except for ZoneService API changes). It goes without saying that the performance is still the same on the light tests, so only heavy tests are retested.
Heavy Test 1 (10K zones, 10K entities)
| Library | FPS |
|---|---|
| QuickZone | 21.60 |
| ZoneService | 200.98 |
Heavy Test 2 (1M zones, 500 entities)
| Library | FPS |
|---|---|
| QuickZone | 13.13 |
| ZoneService | 54.62 |
Heavy Test 3 (10K dynamic zones, 10K entities)
| Library | FPS |
|---|---|
| QuickZone | 14.97 |
| ZoneService | 41.43 |
On heavy test 2 ZoneService is slower due to the new feature that allows the same zone to be under multiple groups. However, thanks to the new adaptive polling feature we can achieve much higher performance (~115 FPS) on this test if we lower the polling rate from 30Hz to 20Hz. I also retested memory usage and the results are the same as before, so I didn’t include them. Likewise, initialization performance is about the same as before or possibly slightly better. The result was still 4.1M zones on the retest.
New API
The API has changed significantly compared to the original release. Therefore I’ve updated the docs to reflect these new changes. Here is an example usage with the new API:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ZoneService = require(ReplicatedStorage.ZoneService)
local Group = ZoneService.Group
local zones = Group.new()
for i, part in workspace.Zones:GetChildren() do
local zone = ZoneService.fromPart(part, {priority = 10, dynamic = false, metadata = "zone"..i})
zones:add(zone)
zone:onEnter(function(player)
print(player.Name.." entered zone!")
part.Color = Color3.new(0, 1, 0)
end
zone:onExit(function(player)
print(player.Name.." exited zone!")
part.Color = Color3.new(0, 0, 0)
end
end
Players.PlayerAdded:Connect(function(player)
local tracked = ZoneService:track(player)
tracked:onZoneChange(zones, function(zone)
print(player.Name.." is in "..tostring(zone and zone.metadata))
end)
end)
Players.PlayerRemoving:Connect(function(player)
ZoneService:untrack(player)
end)