Last active
April 9, 2018 03:29
-
-
Save tiffany352/df016e8394639bb94261517e70f5a76d to your computer and use it in GitHub Desktop.
ECS using Instances and CollectionService
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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