From 58549d6b0ab5ab63eae3b9268404afeae4321145 Mon Sep 17 00:00:00 2001 From: Justin van der Leij Date: Thu, 25 Jan 2018 15:19:31 +0100 Subject: [PATCH] Initial commit --- .gitattributes | 2 + .gitignore | 41 ++++++++++++++ LICENSE | 21 ++++++++ README.md | 1 + conf.lua | 9 ++++ fluid/component.lua | 41 ++++++++++++++ fluid/entity.lua | 75 ++++++++++++++++++++++++++ fluid/eventManager.lua | 79 +++++++++++++++++++++++++++ fluid/init.lua | 82 ++++++++++++++++++++++++++++ fluid/instance.lua | 86 ++++++++++++++++++++++++++++++ fluid/list.lua | 53 ++++++++++++++++++ fluid/pool.lua | 57 ++++++++++++++++++++ fluid/system.lua | 118 +++++++++++++++++++++++++++++++++++++++++ main.lua | 0 14 files changed, 665 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 conf.lua create mode 100644 fluid/component.lua create mode 100644 fluid/entity.lua create mode 100644 fluid/eventManager.lua create mode 100644 fluid/init.lua create mode 100644 fluid/instance.lua create mode 100644 fluid/list.lua create mode 100644 fluid/pool.lua create mode 100644 fluid/system.lua create mode 100644 main.lua diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6fd0a37 --- /dev/null +++ b/.gitignore @@ -0,0 +1,41 @@ +# Compiled Lua sources +luac.out + +# luarocks build files +*.src.rock +*.zip +*.tar.gz + +# Object files +*.o +*.os +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo +*.def +*.exp + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8771a88 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Justin van der Leij + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6f9c351 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Fluid diff --git a/conf.lua b/conf.lua new file mode 100644 index 0000000..d8be786 --- /dev/null +++ b/conf.lua @@ -0,0 +1,9 @@ +function love.conf(t) + t.identity = "Platformer" + t.version = "0.10.2" + t.console = true + + t.window.vsync = false + t.window.width = 720 + t.window.height = 720 +end diff --git a/fluid/component.lua b/fluid/component.lua new file mode 100644 index 0000000..689f516 --- /dev/null +++ b/fluid/component.lua @@ -0,0 +1,41 @@ +local Component = {} +Component.__index = Component + +--- Creates a new Component. +-- @param populate A function that populates the Bag with values +-- @param inherit States if the Bag should inherit the Component's functions +-- @return A Component object +function Component.new(populate, inherit) + local component = setmetatable({ + __populate = populate, + __inherit = inherit, + }, Component) + + if inherit then + component.__mt = {__index = component} + end + + return component +end + +-- Creates and initializes a new Bag. +-- @param ... The values passed to the populate function +-- @return A new initialized Bag +function Component:initialize(...) + if self.__populate then + local bag = {} + self.__populate(bag, ...) + + if self.__inherit then + setmetatable(bag, self.__mt) + end + + return bag + end + + return true +end + +return setmetatable(Component, { + __call = function(_, ...) return Component.new(...) end, +}) diff --git a/fluid/entity.lua b/fluid/entity.lua new file mode 100644 index 0000000..d53e60d --- /dev/null +++ b/fluid/entity.lua @@ -0,0 +1,75 @@ +local Entity = { + entities = {}, +} +Entity.__index = Entity + +--- Creates and initializes a new Entity. +-- @return A new Entity +function Entity.new() + local e = setmetatable({ + id = #Entity.entities + 1, + components = {}, + systems = {}, + keys = {}, + + instance = nil, + }, Entity) + + Entity.entities[e.id] = e + + return e +end + +--- Gives an Entity a component with values. +-- @param component The Component to add +-- @param ... The values passed to the Component +-- @return self +function Entity:give(component, ...) + self.components[component] = component:initialize(...) + + return self +end + +--- Removes a component from an Entity. +-- @param component The Component to remove +-- @return self +function Entity:remove(component) + self.components[component] = nil + + return self +end + +--- Checks the Entity against the pools again. +-- @return self +function Entity:check() + self.instance:checkEntity(self) + + return self +end + +--- Removed an Entity from the instance. +-- @return self +function Entity:destroy() + Entity.entities[self.id] = nil + self.instance:destroyEntity(self) + + return self +end + +--- Gets a Component from the Entity +-- @param component The Component to get +-- @return The Bag from the Component +function Entity:get(component) + return self.components[component] +end + +--- Returns true if the Entity has the Component +-- @params component The Component to check against +-- @return True if the entity has the Bag. False otherwise +function Entity:has(component) + return self.components[component] and true +end + +return setmetatable(Entity, { + __call = function(_, ...) return Entity.new(...) end, +}) diff --git a/fluid/eventManager.lua b/fluid/eventManager.lua new file mode 100644 index 0000000..d7b9540 --- /dev/null +++ b/fluid/eventManager.lua @@ -0,0 +1,79 @@ +local EventManager = {} +EventManager.__index = EventManager + +function EventManager.new() + local eventManager = setmetatable({ + queue = {count = 0}, + listeners = {}, + }, EventManager) + + return eventManager +end + +function EventManager:push(event) + queue.count = queue.count + 1 + queue[queue.count] = event + + return self +end + +function EventManager:emit(name, ...) + local listeners = self.listeners[name] + + if listeners then + for i = 1, #listeners do + local listener = listeners[i] + listener[name](listener, ...) + end + end + + return self +end + +function EventManager:register(name, listener) + local listeners = self.listeners[name] + + if not listeners then + listeners = {count = 0} + self.listeners[name] = listeners + end + + listeners.count = listeners.count + 1 + listeners[listeners.count] = listener + + return self +end + +function EventManager:deregister(name, listener) + local listeners = self.listeners[name] + + if listeners then + for index, other in ipairs(listeners) do + if listener == other then + table.remove(listeners, index) + listeners.count = listeners.count - 1 + + return + end + end + end + + return self +end + +function EventManager:process() + local queue = self.queue + + for i = 1, queue.count do + self:emit(queue[i]) + queue[i] = nil + end + + queue.count = 0 + + return self +end + +return setmetatable(EventManager, { + __call = function(_, ...) return EventManager.new(...) end, +}) diff --git a/fluid/init.lua b/fluid/init.lua new file mode 100644 index 0000000..1d756ae --- /dev/null +++ b/fluid/init.lua @@ -0,0 +1,82 @@ +local PATH = (...):gsub('%.init$', '') + +local Fluid = {} + +function Fluid.init(settings) + Fluid.entity = require(PATH..".entity") + Fluid.component = require(PATH..".component") + Fluid.system = require(PATH..".system") + Fluid.instance = require(PATH..".instance") + Fluid.eventManager = require(PATH..".eventManager") + + if settings and settings.useEvents then + Fluid.instances = {} + + Fluid.addInstance = function(instance) + table.insert(Fluid.instances, instance) + end + + Fluid.removeInstance = function(instance) + for i, instance in ipairs(Fluid.instances) do + table.remove(Fluid.instances, i) + break + end + end + + love.run = function() + if love.math then + love.math.setRandomSeed(os.time()) + love.timer.step() + end + + for _, instance in ipairs(Fluid.instances) do + instance:emit("load", arg) + end + + if love.timer then love.timer.step() end + + local dt = 0 + + while true do + if love.event then + love.event.pump() + for name, a, b, c, d, e, f in love.event.poll() do + for _, instance in ipairs(Fluid.instances) do + instance:emit(name, a, b, c, d, e, f) + end + + if name == "quit" then + return a + end + end + end + + if love.timer then + love.timer.step() + dt = love.timer.getDelta() + end + + for _, instance in ipairs(Fluid.instances) do + instance:emit("update", dt) + end + + if love.graphics and love.graphics.isActive() then + love.graphics.clear(love.graphics.getBackgroundColor()) + love.graphics.origin() + + for _, instance in ipairs(Fluid.instances) do + instance:emit("draw") + end + + love.graphics.present() + end + + if love.timer then love.timer.sleep(0.001) end + end + end + end + + return Fluid +end + +return Fluid diff --git a/fluid/instance.lua b/fluid/instance.lua new file mode 100644 index 0000000..a80f4ae --- /dev/null +++ b/fluid/instance.lua @@ -0,0 +1,86 @@ +local PATH = (...):gsub('%.[^%.]+$', '') + +local Pool = require(PATH..".pool") +local EventManager = require(PATH..".eventManager") + +local Instance = {} +Instance.__index = Instance + +function Instance.new() + local instance = setmetatable({ + entities = Pool(), + eventManager = EventManager(), + + systems = {}, + namedSystems = {}, + }, Instance) + + return instance +end + +function Instance:addEntity(e) + e.instance = self + self.entities:add(e) + self:checkEntity(e) +end + +function Instance:checkEntity(e) + for _, system in ipairs(self.systems) do + if system:entityUpdated(e) then + e.systems[#e.systems + 1] = system + end + end +end + +function Instance:destroyEntity(e) + self.entities:remove(e) + + for _, system in ipairs(e.systems) do + system:remove(e) + end +end + +function Instance:addSystem(system, eventName) + if not self.namedSystems[system] then + self.systems[#self.systems + 1] = system + self.namedSystems[system] = system + end + + self.eventManager:register(eventName, system) + + return self +end + +function Instance:removeSystem(system) + for index, other in ipairs(self.systems) do + if system == other then + table.remove(self.systems, index) + end + end + + self.namedSystems[system] = nil + + return self +end + +function Instance:emit(...) + self.eventManager:emit(...) + + return self +end + +function Instance:update(dt) + self:emit("update", dt) + + return self +end + +function Instance:draw() + self:emit("draw") + + return self +end + +return setmetatable(Instance, { + __call = function(_, ...) return Instance.new(...) end, +}) diff --git a/fluid/list.lua b/fluid/list.lua new file mode 100644 index 0000000..50e7c41 --- /dev/null +++ b/fluid/list.lua @@ -0,0 +1,53 @@ +local List = {} +local mt = {__index = List} + +function List.new() + return setmetatable({ + numerical = {}, + named = {}, + size = 0, + }, mt) +end + +function List:clear() + self.numerical = {} + self.named = {} + self.size = 0 +end + +function List:add(obj) + local size = self.size + 1 + + self.numerical[size] = obj + self.named[obj] = size + self.size = size +end + +function List:remove(obj) + local index = self.named[obj] + local size = self.size + + if index == size then + self.numerical[size] = nil + else + local other = self.numerical[size] + + self.numerical[index] = other + self.named[other] = index + end + + self.named[obj] = nil + self.size = size - 1 +end + +function List:get(i) + return self.numerical[i] +end + +function List:getIndex(obj) + return self.named[obj] +end + +return setmetatable(List, { + __call = function() return List.new() end, +}) diff --git a/fluid/pool.lua b/fluid/pool.lua new file mode 100644 index 0000000..8b87d48 --- /dev/null +++ b/fluid/pool.lua @@ -0,0 +1,57 @@ +local Pool = {} +Pool.__index = Pool + +function Pool.new(name, filter) + local pool = setmetatable({ + __filter = filter, + __name = name, + }, Pool) + + return pool +end + +function Pool:eligible(e) + for _, component in ipairs(self.__filter) do + if not e.components[component] then + return false + end + end + + return true +end + +function Pool:add(e) + local key = #self + 1 + + self[key] = e + e.keys[self] = key +end + +function Pool:has(e) + return e.keys[self] and true +end + +function Pool:remove(e, pool) + local key = e.keys[self] + + if key then + local count = #self + + if key == count then + self[key] = nil + e.keys[self] = nil + else + local swap = self[count] + + self[key] = swap + self[count] = nil + swap.keys[self] = key + end + + return true + end +end + +return setmetatable(Pool, { + __call = function(_, ...) return Pool.new(...) end, +}) diff --git a/fluid/system.lua b/fluid/system.lua new file mode 100644 index 0000000..414fb08 --- /dev/null +++ b/fluid/system.lua @@ -0,0 +1,118 @@ +local PATH = (...):gsub('%.[^%.]+$', '') + +local Component = require(PATH..".component") +local Pool = require(PATH..".pool") + +local System = {} +System.__index = System + +function System.new(...) + local system = setmetatable({ + __all = {}, + __pools = {}, + }, System) + + for _, filter in pairs({...}) do + local pool = system:buildPool(filter) + if not system[pool.__name] then + system[pool.__name] = pool + system.__pools[#system.__pools + 1] = pool + else + error("Pool with name '"..pool.__name.."' already exists.") + end + end + + return system +end + +function System:buildPool(pool) + local name = "pool" + local filter = {} + + for i, v in ipairs(pool) do + if type(v) == "table" then + filter[#filter + 1] = v + elseif type(v) == "string" then + name = v + end + end + + return Pool(name, filter) +end + +function System:entityUpdated(e) + local systemHas = self:has(e) + + for _, pool in ipairs(self.__pools) do + local poolHas = pool:has(e) + local eligible = pool:eligible(e) + + if not poolHas and eligible then + pool:add(e) + self:entityAddedTo(e, pool) + self:tryAdd(e) + + return true + elseif poolHas and not eligible then + pool:remove(e) + self:entityRemovedFrom(e, pool) + self:tryRemove(e) + + return false + end + end +end + +function System:tryAdd(e) + if not self:has(e) then + self.__all[e] = 0 + self:entityAdded(e) + end + + self.__all[e] = self.__all[e] + 1 +end + +function System:tryRemove() + if self:has(e) then + self.__all[e] = self.__all[e] - 1 + + if self.__all[e] == 0 then + self.__all[e] = nil + self:entityRemoved(e) + end + end +end + +function System:remove(e) + if self:has(e) then + for _, pool in ipairs(self.__pools) do + if pool:has(e) then + pool:remove(e) + self:entityRemovedFrom(e, pool) + end + end + + self.__all[e] = nil + self:entityRemoved(e) + end +end + +function System:has(e) + return self.__all[e] and true +end + +function System:entityAdded(e) +end + +function System:entityAddedTo(e, pool) +end + +function System:entityRemoved(e) +end + +function System:entityRemovedFrom(e, pool) +end + +return setmetatable(System, { + __call = function(_, ...) return System.new(...) end, +}) diff --git a/main.lua b/main.lua new file mode 100644 index 0000000..e69de29