Skip to content

Instantly share code, notes, and snippets.

@tiffany352
Last active April 9, 2018 03:29
Show Gist options
  • Save tiffany352/df016e8394639bb94261517e70f5a76d to your computer and use it in GitHub Desktop.
Save tiffany352/df016e8394639bb94261517e70f5a76d to your computer and use it in GitHub Desktop.
ECS using Instances and CollectionService
local CollectionService = game:GetService("CollectionService")
local RunService = game:GetService("RunService")
local Symbol = require(script.Parent.Symbol)
local Maid = require(script.Parent.Maid)
local Component = {}
Component.__index = Component
function Component:getComponent(component)
return component._instances[self.instance]
end
function Component:getComponentInParent(component)
-- objects removed from the data model will have the instance removed signal fired on them, so the component will be detached
assert(self.instance.Parent, "Component's instance has no parent")
return component._instances[self.instance.Parent]
end
function Component:getComponentsInDescendants(component)
local components = {}
for _,instance in pairs(self.instance:GetDescendants()) do
local object = component._instances[instance]
if object then
components[#components+1] = object
end
end
return components
end
function Component:added()
end
function Component:remove()
end
Component._components = {}
Component._queued = {}
Component.AddedFlag = Symbol.new("wasAdded")
function Component:extend(name, defaultProps)
local Class = {}
Class.__index = Class
setmetatable(Class, self)
-- begin only magical part of :extend()
Class.className = name
Class._instances = {}
self._components[name] = Class
-- end magic
function Class.new(instance)
local component = {
instance = instance,
maid = Maid.new(),
}
for key, value in pairs(defaultProps or {}) do
component[key] = value
end
setmetatable(component, Class)
return component
end
function Class:destroy()
self.maid:clean()
end
return Class
end
function Component._setup()
for _,component in pairs(Component._components) do
local function attach(instance)
local object = component.new(instance)
object[Component.AddedFlag] = false
component._instances[instance] = object
Component._queued[#Component._queued+1] = object
return component
end
local function detach(instance)
local object = component._instances[instance]
if object[Component.AddedFlag] then
object:removed()
end
object:destroy()
component._instances[instance] = nil
end
for _,instance in pairs(CollectionService:GetTagged(component.className)) do
attach(instance)
end
CollectionService:GetInstanceAddedSignal(component.className):Connect(attach)
CollectionService:GetInstanceRemovedSignal(component.className):Connect(detach)
end
end
function Component._doStepped(dt)
for _,component in pairs(Component._components) do
-- only step components that have a stepped method
if component.stepped then
debug.profilebegin(string.format("Stepping %s components", component.className))
for _, object in pairs(component._instances) do
object:stepped(dt)
end
debug.profileend()
end
end
end
-- call this as late as possible, or at least after _doStepped
function Component._processQueue()
local queue = Component._queued
Component._queued = {}
for i = 1, #queue do
local comp = queue[i]
if not comp[Component.AddedFlag] then
comp[Component.AddedFlag] = true
queue[i]:added()
end
end
end
return Component
local Component = require(script.Parent.Component)
local FireEffect = require(script.Parent.FireEffect) -- N.B.
local DeathBrick = Component:extend("DeathBrick")
function DeathBrick:added()
self.maid.touchedConn = self.instance.Touched:Connect(function(part)
local humanoid = part.Parent and part.Parent:FindFirstChildOfClass("Humanoid")
if humanoid then
humanoid.Health = 0
else
part:BreakJoints()
end
local fireEffect = self:getComponent(FireEffect) -- N.B.
if fireEffect then
fireEffect:boost()
end
end)
end
return DeathBrick
local TweenService = game:GetService("TweenService")
local Component = require(script.Parent.Component)
local tweenInfo = TweenInfo.new(0.4, Enum.EasingStyle.Quad)
local FireEffect = Component:extend("FireEffect", {
size = 2,
})
function FireEffect:added()
local fire = Instance.new("Fire")
fire.Name = "FireEffect"
fire.Size = self.size
fire.Parent = self.instance
self.maid.effect = fire
end
function FireEffect:boost()
if self.tween then
self.tween:Cancel()
end
local fire = self.maid.effect
fire.Size = fire.Size * 2.0
self.tween = TweenService:Create(self.maid.effect, tweenInfo, { Size = self.size })
self.tween:Play()
end
return FireEffect
-- A helper utility based on code by Quenty.
local Maid = {}
function Maid.new()
local self = {
_tasks = {},
}
setmetatable(self, Maid)
return self
end
function Maid:__index(key)
return Maid[key] or self._tasks[key]
end
function Maid:__newindex(key, newTask)
if Maid[key] then
error(string.format("Cannot use %q as a Maid key", tostring(key)))
end
local tasks = self._tasks
local oldTask = tasks[key]
tasks[key] = newTask
if oldTask then
Maid.cleanupTask(oldTask)
end
end
function Maid:give(task)
local tasks = self._tasks
tasks[#tasks+1] = task
end
function Maid.cleanupTask(task)
local taskTy = typeof(task)
if taskTy == 'function' then
task()
elseif taskTy == 'RBXScriptConnection' then
task:Disconnect()
elseif taskTy == 'Instance' then
task:Destroy()
elseif task.destroy then
task:destroy()
else
error("Unable to cleanup unknown task")
end
end
function Maid:clean()
local tasks = self._tasks
for key,task in pairs(tasks) do
if typeof(task) == 'RBXScriptConnection' then
tasks[key] = nil
task:Disconnect()
end
end
local index, task = next(tasks)
while task ~= nil do
tasks[index] = nil
Maid.cleanupTask(task)
index, task = next(tasks)
end
end
Maid.destroy = Maid.clean
return Maid
local Source = game.ReplicatedStorage.Source
local Component = require(Source.Common.Component)
require(Source.Common.DeathBrick)
require(Source.Common.FireEffect)
Component._setup()
game:GetService("RunService").Heartbeat:Connect(function(dt)
Component._doStepped(dt)
Component._processQueue()
end)
local Symbol = {}
function Symbol.new(name)
local self = {
__name = name,
}
setmetatable(self, Symbol)
return self
end
function Symbol:__index(key)
error(string.format("Cannot index Symbol with key %q", tostring(key)))
end
function Symbol:__newindex(key, value)
error(string.format("Cannot assign key %q on immutable Symbol", tostring(key)))
end
function Symbol:__tostring()
return string.format("<Symbol:%s>", self.__name)
end
return Symbol
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment