Skip to content

Commit

Permalink
Fixed character parts memory leak and added zone:onItemEnter/Exit met…
Browse files Browse the repository at this point in the history
…hods
  • Loading branch information
1ForeverHD committed Sep 7, 2021
1 parent 04d6483 commit 948f9e2
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 27 deletions.
28 changes: 28 additions & 0 deletions docs/api/zone.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,34 @@ zone:relocate()
```
Moves the zone outside of workspace into a separate WorldModel within ReplicatedStorage or ServerStorage. This action is irreversible - once called it cannot be undone.

----
#### onItemEnter
```lua
zone:onItemEnter(characterOrBasePart, callbackFunction)
```
Tracks the item until it has entered the zone, then calls the given function. If the item is already within the zone, the given function is called right away.

```lua
local item = character:FindFirstChild("HumanoidRootPart")
zone:onItemEnter(item, function()
print("The item has entered the zone!"))
end)
```

----
#### onItemExit
```lua
zone:onItemExit(characterOrBasePart, callbackFunction)
```
Tracks the item until it has exited the zone, then calls the given function. If the item is already outside the zone, the given function is called right away.

```lua
local item = character:FindFirstChild("HumanoidRootPart")
zone:onItemExit(item, function()
print("The item has exited the zone!"))
end)
```

----
#### destroy
```lua
Expand Down
49 changes: 48 additions & 1 deletion docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
## [3.2.0] - September 7 2021
### Added
- ``Zone:onItemEnter(characterOrBasePart, callbackFunction)``
- ``Zone:onItemExit(characterOrBasePart, callbackFunction)``
- An error warning when a zone is constructed using parts that don't belong to the Default collision group
- Support for non-basepart HeadParts

### Changed
- Reorganised checker parts

### Fixed
- A bug preventing the disconnection of tracked character parts which resulted in a slight memory leak whenever a player reset or changed bodyparts



--------
## [3.1.0] - August 28 2021
### Added
- ``Zone.fromRegion(cframe, size)``
Expand Down Expand Up @@ -148,4 +164,35 @@

### Fixed
- Rotational and complex geometry detection
- ``getRandomPoints()`` inaccuracies
- ``getRandomPoints()`` inaccuracies



```
-- This constructs a zone based upon a group of parts in Workspace and listens for when a player enters and exits this group
local container = workspace.AModelOfPartsRepresentingTheZone
local zone = Zone.new(container)
zone.playerEntered:Connect(function(player)
print(("%s entered the zone!"):format(player.Name))
end)
zone.playerExited:Connect(function(player)
print(("%s exited the zone!"):format(player.Name))
end)
```

```
-- This constructs a zone based upon a region, tracks a Zombie NPC, then listens for when the item (aka the Zombie) enters and exits the zone.
local zoneCFrame = CFrame.new()
local zoneSize = Vector3.new(100, 100, 100)
local zone = Zone.fromRegion(zoneCFrame, zoneSize)
zone.itemEntered:Connect(function(item)
print(("%s entered the zone!"):format(item.Name))
end)
zone.itemExited:Connect(function(item)
print(("%s exited the zone!"):format(item.Name))
end)
```
5 changes: 5 additions & 0 deletions docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@

-------------------------------------

### Sliding Doors
<video src="https://giant.gfycat.com/PastelPastBlueshark.mp4" width="100%" controls></video>

-------------------------------------

All examples can be tested, viewed and edited at the [Playground](https://www.roblox.com/games/6166477769/ZonePlus-Playground).
6 changes: 5 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[Zone API]: https://1foreverhd.github.io/ZonePlus/api/zone/
[Accuracy Enum]: https://github.com/1ForeverHD/ZonePlus/blob/main/src/Zone/Enum/Accuracy.lua
[Detection Enum]: https://github.com/1ForeverHD/ZonePlus/blob/main/src/Zone/Enum/Detection.lua
[zone:relocate()]: https://1foreverhd.github.io/ZonePlus/api/zone/#relocate

## Summary

Expand Down Expand Up @@ -44,7 +45,10 @@ end)
On the client you may only wish to listen for the LocalPlayer (such as for an ambient system). To achieve this you would alternatively use the ``.localPlayer`` events.

!!! important
Zone group parts must remain within the workspace for zones to fully work
Initially zone parts should be located within Workspace to function properly. If you wish to move zones outside of Workspace (e.g. to prevent them interacting with other parts), consider using [zone:relocate()].

!!! important
Zone parts must belong to the 'Default' (0) collision group.

If you don't intend to frequently check for items entering and exiting a zone, you can utilise zone methods:

Expand Down
2 changes: 1 addition & 1 deletion src/Zone/VERSION.lua
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-- v3.1.0
-- v3.2.0
34 changes: 23 additions & 11 deletions src/Zone/ZoneController/Tracker.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ local players = game:GetService("Players")
local runService = game:GetService("RunService")
local heartbeat = runService.Heartbeat
local Signal = require(script.Parent.Parent.Signal)
local Janitor = require(script.Parent.Parent.Janitor)



Expand Down Expand Up @@ -43,6 +44,9 @@ function Tracker.getCharacterSize(character)
local head = character and character:FindFirstChild("Head")
local hrp = character and character:FindFirstChild("HumanoidRootPart")
if not(hrp and head) then return nil end
if not head:IsA("BasePart") then
head = hrp
end
local headY = head.Size.Y
local hrpSize = hrp.Size
local charSize = (hrpSize * Vector3.new(2, 2, 1)) + Vector3.new(0, headY, 0)
Expand All @@ -66,6 +70,7 @@ function Tracker.new(name)
self.characters = {}
self.baseParts = {}
self.exitDetections = {}
self.janitor = Janitor.new()

if name == "player" then
local function updatePlayerCharacters()
Expand Down Expand Up @@ -178,7 +183,7 @@ function Tracker:update()
self.parts = {}
self.partToItem = {}
self.items = {}

-- This tracks the bodyparts of a character
for character, _ in pairs(self.characters) do
local charSize = Tracker.getCharacterSize(character)
Expand All @@ -188,21 +193,28 @@ function Tracker:update()
local rSize = charSize
local charVolume = rSize.X*rSize.Y*rSize.Z
self.totalVolume += charVolume

local characterJanitor = self.janitor:add(Janitor.new(), "destroy", "trackCharacterParts-"..self.name)
local function updateTrackerOnParentChanged(instance)
characterJanitor:add(instance.AncestryChanged:Connect(function()
if not instance:IsDescendantOf(game) then
if instance.Parent == nil and characterJanitor ~= nil then
characterJanitor:destroy()
characterJanitor = nil
self:update()
end
end
end), "Disconnect")
end

for _, part in pairs(character:GetChildren()) do
if part:IsA("BasePart") and not Tracker.bodyPartsToIgnore[part.Name] then
self.partToItem[part] = character
table.insert(self.parts, part)
local connection
local isDisconnected = false
connection = part:GetPropertyChangedSignal("Parent"):Connect(function()
if part.Parent == nil and not isDisconnected then
isDisconnected = true
connection:Disconnect()
self:update()
end
end)
updateTrackerOnParentChanged(part)
end
end
updateTrackerOnParentChanged(character)
table.insert(self.items, character)
end

Expand All @@ -215,7 +227,7 @@ function Tracker:update()
table.insert(self.parts, additionalPart)
table.insert(self.items, additionalPart)
end

-- This creates the whitelist so that
self.whitelistParams = OverlapParams.new()
self.whitelistParams.FilterType = Enum.RaycastFilterType.Whitelist
Expand Down
13 changes: 13 additions & 0 deletions src/Zone/ZoneController/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,19 @@ function ZoneController.getGroup(settingsGroupName)
return settingsGroups[settingsGroupName]
end

local workspaceContainer
local workspaceContainerName = string.format("ZonePlus%sContainer", (runService:IsClient() and "Client") or "Server")
function ZoneController.getWorkspaceContainer()
local container = workspaceContainer or workspace:FindFirstChild(workspaceContainerName)
if not container then
container = Instance.new("Folder")
container.Name = workspaceContainerName
container.Parent = workspace
workspaceContainer = container
end
return container
end



return ZoneController
109 changes: 96 additions & 13 deletions src/Zone/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -74,15 +74,8 @@ function Zone.new(container)
self.trackedItems = {}
self.settingsGroupName = nil
self.worldModel = workspace

local checkerPart = janitor:add(Instance.new("Part"), "Destroy")
checkerPart.Size = Vector3.new(0.1, 0.1, 0.1)
checkerPart.Name = "ZonePlusCheckerPart"
checkerPart.Anchored = true
checkerPart.Transparency = 1
checkerPart.CanCollide = false
checkerPart.Parent = workspace
self.checkerPart = checkerPart
self.onItemDetails = {}
self.itemsToUntrack = {}

-- This updates _currentEnterDetection and _currentExitDetection right away to prevent nil comparisons
ZoneController.updateDetection(self)
Expand Down Expand Up @@ -344,10 +337,19 @@ function Zone:_update()
end
end
local partProperties = {"Size", "Position"}
local function verifyDefaultCollision(instance)
if instance.CollisionGroupId ~= 0 then
error("Zone parts must belong to the 'Default' (0) CollisionGroup! Consider using zone:relocate() if you wish to move zones outside of workspace to prevent them interacting with other parts.")
end
end
for _, part in pairs(zoneParts) do
for _, prop in pairs(partProperties) do
self._updateConnections:add(part:GetPropertyChangedSignal(prop):Connect(update), "Disconnect")
end
verifyDefaultCollision(part)
self._updateConnections:add(part:GetPropertyChangedSignal("CollisionGroupId"):Connect(function()
verifyDefaultCollision(part)
end), "Disconnect")
end
local containerEvents = {"ChildAdded", "ChildRemoved"}
for _, holder in pairs(holders) do
Expand Down Expand Up @@ -592,13 +594,36 @@ function Zone:findPart(part)
return false
end

function Zone:getCheckerPart()
local checkerPart = self.checkerPart
if not checkerPart then
checkerPart = self.janitor:add(Instance.new("Part"), "Destroy")
checkerPart.Size = Vector3.new(0.1, 0.1, 0.1)
checkerPart.Name = "ZonePlusCheckerPart"
checkerPart.Anchored = true
checkerPart.Transparency = 1
checkerPart.CanCollide = false
self.checkerPart = checkerPart
end
local checkerParent = self.worldModel
if checkerParent == workspace then
checkerParent = ZoneController.getWorkspaceContainer()
end
if checkerPart.Parent ~= checkerParent then
checkerPart.Parent = checkerParent
end
return checkerPart
end

function Zone:findPoint(positionOrCFrame)
local cframe = positionOrCFrame
if typeof(positionOrCFrame) == "Vector3" then
cframe = CFrame.new(positionOrCFrame)
end
self.checkerPart.CFrame = cframe
local methodName, args = self:_getRegionConstructor(self.checkerPart, self.overlapParams.zonePartsWhitelist)
local checkerPart = self:getCheckerPart()
checkerPart.CFrame = cframe
--checkerPart.Parent = self.worldModel
local methodName, args = self:_getRegionConstructor(checkerPart, self.overlapParams.zonePartsWhitelist)
local touchingZoneParts = self.worldModel[methodName](self.worldModel, unpack(args))
--local touchingZoneParts = self.worldModel:GetPartsInPart(self.checkerPart, self.overlapParams.zonePartsWhitelist)
if #touchingZoneParts > 0 then
Expand Down Expand Up @@ -713,6 +738,9 @@ function Zone:trackItem(instance)
if self.trackedItems[instance] then
return
end
if self.itemsToUntrack[instance] then
self.itemsToUntrack[instance] = nil
end

local itemJanitor = self.janitor:add(Janitor.new(), "destroy")
local itemDetail = {
Expand All @@ -723,8 +751,8 @@ function Zone:trackItem(instance)
}
self.trackedItems[instance] = itemDetail

itemJanitor:add(instance:GetPropertyChangedSignal("Parent"):Connect(function()
if instance.Parent == nil then
itemJanitor:add(instance.AncestryChanged:Connect(function()
if not instance:IsDescendantOf(game) then
self:untrackItem(instance)
end
end), "Disconnect")
Expand Down Expand Up @@ -782,6 +810,61 @@ function Zone:relocate()
relocationContainer.Parent = worldModel
end

function Zone:_onItemCallback(eventName, desiredValue, instance, callbackFunction)
local detail = self.onItemDetails[instance]
if not detail then
detail = {}
self.onItemDetails[instance] = detail
end
if #detail == 0 then
self.itemsToUntrack[instance] = true
end
table.insert(detail, instance)
self:trackItem(instance)

local function triggerCallback()
callbackFunction()
if self.itemsToUntrack[instance] then
self.itemsToUntrack[instance] = nil
self:untrackItem(instance)
end
end

local inZoneAlready = self:findItem(instance)
if inZoneAlready == desiredValue then
triggerCallback()
else
local connection
connection = self[eventName]:Connect(function(item)
if connection and item == instance then
connection:Disconnect()
connection = nil
triggerCallback()
end
end)
--[[
if typeof(expireAfterSeconds) == "number" then
task.delay(expireAfterSeconds, function()
if connection ~= nil then
print("EXPIRE!")
connection:Disconnect()
connection = nil
triggerCallback()
end
end)
end
--]]
end
end

function Zone:onItemEnter(...)
self:_onItemCallback("itemEntered", true, ...)
end

function Zone:onItemExit(...)
self:_onItemCallback("itemExited", false, ...)
end

function Zone:destroy()
self:unbindFromGroup()
self.janitor:destroy()
Expand Down

0 comments on commit 948f9e2

Please sign in to comment.