I’m working on getting used to using --!strict in my code, and I’m trying to figure out this:
function self:Destroy(): ()
self.Maid:DoCleaning()
self = nil
end
This is the Destroy method for my OOP class. It does work, but self = nil is flagged as a type error, because nil can’t be converted to the Entity class I casted, so what’s the ‘right’ way of going about destroying the object? The class is a dictionary with some values.
There’s a lot of flags with my constructor method because self could be nil, I’m going to try rewriting the constructor method and see if that sorts everything out.
Initially I was against this, I feel like casting any defeats the point of using strict typing, but it isn’t like I’ll be using that value again so it doesn’t really matter. I’m going to wait a bit, then if there are no preferable solutions then I’ll mark this as the solution.
--!strict
--- Services & Modules
local HttpService = game:GetService("HttpService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Maid = require(ReplicatedStorage.Utility.Maid)
--- Types
export type Entity = {
EntityId: string,
Model: Model,
StateMachine: nil, -- Temporary
Maid: any,
Destroy: (self: Entity) -> ()
}
export type EntityData = {
Model: Model
}
export type EntityModule = {
Start: () -> (),
new: (entityId: string, location: Vector3, additionalData: any?) -> Entity?,
GetEntityById: (entityId: string) -> Entity?
}
--- Module
local module = {} :: EntityModule
--- Variables
local loaded: boolean = false
local cachedEntityData: {[string]: EntityData} = {}
local entities: {[string]: Entity} = {}
setmetatable(entities, {__mode = "kv"}) -- Table values are weak, so they are removed when the entity is destroyed.
--- Functions
function getEntityId(entityModel: Model): string
local id: string
repeat id = HttpService:GenerateGUID(false) until entities[id] == nil
return id
end
--- Module Functions
function module.Start(): ()
-- Load animal templates.
for _, template in script.EntityData:GetChildren() do
if not template:IsA("ModuleScript") then
continue
end
cachedEntityData[string.sub(template.Name, 2, #template.Name)] = require(template) :: EntityData
end
loaded = true
end
function module.new(entityId: string, location: Vector3, additionalData: any?): Entity?
if not (entityId and location) then
error(`{script.Name} | Missing parameters.`)
return
end
-- Make sure all entity data is loaded.
if not loaded then
repeat task.wait(.001) until loaded
end
-- Make sure entity ID is real.
local entityDataToConstruct = cachedEntityData[entityId]
if not entityDataToConstruct then
print(cachedEntityData)
error(`{script.Name} | Attempt to create new entity with unknown entity ID '{entityId}'.`)
return
end
-- Construct entity.
local self = {} :: Entity
self.Model = entityDataToConstruct.Model:Clone() :: Model
self.Model.Parent = workspace.Entities
local humrp = self.Model:WaitForChild("HumanoidRootPart") :: BasePart
humrp.Position = location
self.StateMachine = nil -- Temporary.
-- Give entity id.
self.EntityId = getEntityId(self.Model)
self.Model:SetAttribute("EntityId", self.EntityId)
entities[self.EntityId] = self
-- Construct maid.
self.Maid = Maid.new()
self.Maid:GiveTask(self.Model)
-- self.Maid:GiveTask(self.StateMachine) -- Temporary as well.
function self:Destroy(): ()
self.Maid:DoCleaning()
self = nil :: any
end
return self
end
function module.GetEntityById(entityId: string): Entity?
return entities[entityId]
end
---
return module
So the fix I suggested earlier would still silence the type checker but I am not sure if it would clean up the object.
You are basically inserting a unique Destroy function into your object. Not sure if this is what you want, unless the object is a singleton, you are probably wasting memory by inserting the same repeated function into each object.
Another thing, I do not see the point of doing self = nil in this function, at all. You are accessing the local parameter self of the Destroy function not the external local variable. Setting either to nil however, won’t guarantee that the table representing your object is collected by the garbage collector unless all references to the table are set to nil.
So if you are accessing one of these objects, outside the script, for example. Calling :Destroy() won’t be enough, you also have to set that reference to nil, if my knowledge of lua is correct.
Forgot to mention, the references inside the module.new() function, unless there’s a closure I missed, will be cleaned anyways after the constructor returns. So don’t worry about setting those references to nil. Find where this return value goes, that is, where this constructor is actually being called, and make that reference nil after calling :Destroy() on it.
This would make a global function Destroy (I know you are doing self.Destroy = Destroy but you’ll still be wasting a bit of memory storing that reference inside each object) you’ll need to put it in the Entity module, like so:
function Entity.Destroy(self: Entity): () ... end
(Just make that change to function header)
Now outside, if you want to do entity:Destroy(). You’ll need to do some stuff with metatables and __index that will make your types pretty complicated. (unless you are using the new type solver)
An easier way would be to just do Entity.Destroy(entity), and I am pretty sure updating your types for this won’t be so hard.