Eiyeron Fulmincendii пре 8 година
комит
5ef2cc7c82

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+*.rock
+/rocks

+ 10 - 0
.vscode/tasks.json

@@ -0,0 +1,10 @@
+{
+    // See https://go.microsoft.com/fwlink/?LinkId=733558
+    // for the documentation about the tasks.json format
+    "version": "0.1.0",
+    "command": "love",
+    "isBackground": false,
+    "args": ["."],
+    "showOutput": "always"
+
+}

+ 61 - 0
class/init.lua

@@ -0,0 +1,61 @@
+--[[
+    Class system
+    @author : Siapran Candoris
+    Supports memoized multiple inheritance
+]]--
+
+local ipairs = ipairs
+
+do
+	local function metatable_search( k, list )
+		for _,e in ipairs(list) do
+			local v = e[k]
+			if v then return v end
+		end
+	end
+
+	local function metatable_cache( self, k )
+		local v = metatable_search(k, self.__parents)
+		self[k] = v
+		return v
+	end
+
+	-- genealogy is
+	local function make_genealogy( self, res, has )
+		res = res or {}
+		has = has or {}
+		local parents = self.__parents
+		if has[self] then
+			return
+		end
+		if parents and #parents > 0 then
+			for _,parent in ipairs(parents) do
+				make_genealogy(parent, res, has)
+			end
+		end
+		res[#res + 1] = self
+		has[self] = true
+		return res
+	end
+
+	-- make a class with simple or multiple inheritance
+	-- inheritance is implemented as cached first found
+	-- do NOT change class methods at runtime, just don't
+	function make_class( ... )
+		local res = {}
+		res.__parents = {...}
+		res.__genealogy = make_genealogy(res)
+		res.__instanceof_cache = {}
+		-- inherited methods are cached to improve runtime performance
+		-- caching is done per class, not per object
+		if #res.__parents > 0 then
+			setmetatable(res, {__index = metatable_cache})
+		end
+
+		res.__index = res
+
+		return res
+	end
+end
+
+return make_class

+ 53 - 0
class/object.lua

@@ -0,0 +1,53 @@
+--[[
+    Object base class
+    @author : Siapran Candoris
+    Features instance of class determination and prototype function (init)
+]]--
+
+local class = require("class")
+--------------------------------
+-- object class
+--
+local object = class()
+
+function object:new( ... )
+	local res = {}
+	setmetatable(res, self)
+	if res.init then
+		res:init(...)
+	end
+	return res
+end
+
+function object:instanceof( class )
+	local cache = self.__instanceof_cache
+	if cache[class] ~= nil then
+		return cache[class]
+	else
+		for _,v in ipairs(self.__genealogy) do
+			if class == v then
+				cache[class] = true
+				return true
+			end
+		end
+		cache[class] = false
+		return false
+	end
+end
+
+-- asssign unique ids to tables
+do
+    local cache = setmetatable({}, {__mode = "k"})
+    local id = 0
+    function identifier( table )
+        if cache[table] then
+            return cache[table]
+        else
+            cache[table] = id
+            id = id + 1
+            return id
+        end
+    end
+end
+
+return object

+ 12 - 0
conf.lua

@@ -0,0 +1,12 @@
+if love.filesystem then
+	require 'rocks' ()
+end
+
+function love.conf(t)
+	t.identity = "ECS"
+	t.version = "0.10.2"
+	t.dependencies = {
+        "inspect = 3.1.0-1",
+        "love-imgui = 0.7-1"
+	}
+end

+ 48 - 0
debug_overlay/console/commands.lua

@@ -0,0 +1,48 @@
+Commands = {
+    cls = {
+        fun = function(console)
+            console.logs = {}
+            console.scroll_to_bottom = true
+        end,
+        help = "Clears the log"
+    },
+    help = {
+        fun = function(console, fun)
+            if fun then
+                if console.command_helps[fun] then
+                    console:log(fun .. " " .. console.command_helps[fun])
+                elseif console.commands[fun] then
+                    console:log(fun.." doesn't have help info.")
+                else
+                    console:log("Command not found : "..fun)
+                end
+                return
+            end
+            local res = ""
+            for k,v in pairs(console.commands) do
+                res = res .. k .. " "
+            end
+            console:log(res)
+        end,
+        help = "[fun] : List commands or show help of a given command."
+    },
+    collectgarbage = {
+        fun = function(console, opt, arg)
+            collectgarbage(opt, arg)
+        end,
+        help = "[opt][, arg] : calls lua's collectgarbage'"
+    },
+    spawn = {
+        fun = function(console)
+            local entity = engine.world:createEntity()
+            console:log("Created entity id : "..entity.__eid)
+        end,
+        help = "Spawn a new entity"
+    }
+
+}
+
+-- Aliases
+Commands.ls = Commands.help
+
+return Commands

+ 174 - 0
debug_overlay/console/init.lua

@@ -0,0 +1,174 @@
+local class =  require("class")
+local DebugWindow = require("debug_overlay.debugwindow")
+require "imgui"
+
+local commands = require("debug_overlay.console.commands")
+local Console = class(DebugWindow)
+
+local INFO = 1
+local DEBUG = 2
+local LOG = 3
+local WARN = 4
+local ERROR = 5
+
+local function bind(new_self, fun, ...)
+    return function(...)
+        fun(new_self, ...)
+    end
+end
+
+local function splitArgs(text)
+    local result = {}
+    local e = 0
+    while true do
+        local b = e+1
+        b = text:find("%S",b)
+        if b==nil then break end
+        if text:sub(b,b)=="'" then
+            e = text:find("'",b+1)
+            b = b+1
+        elseif text:sub(b,b)=='"' then
+            e = text:find('"',b+1)
+            b = b+1
+        else
+            e = text:find("%s",b+1)
+        end
+        if e==nil then e=#text+1 end
+        result[#result+1] = text:sub(b,e-1)
+    end
+    return result
+end
+
+function Console:init()
+    self.shortcut = "f12"
+    self.logs = {}
+    self.logs_filter_toggles = {}
+    self.visible = false;
+    self.input_text = "";
+    self.commands = {}
+    self.command_helps = {}
+    self.take_focus = false
+    self.scroll_to_bottom = true
+
+    for i=1,5 do
+        self.logs_filter_toggles[i] = true
+    end
+
+    for i,v in pairs(commands) do
+        self:registerCommand(i, bind(self, commands[i].fun), v.help)
+    end
+
+end
+
+function Console:render()
+    imgui.SetNextWindowPos(0, love.graphics.getHeight() - 200, 1);
+    imgui.SetNextWindowSize(love.graphics.getWidth(), 200);
+
+    imgui.Begin("Console", nil, {"NoCollapse", "NoResize", "NoMove", "NoTitleBar"})
+
+    if imgui.Button("Bottom") then self.scroll_to_bottom = true end
+    for k,v in ipairs({"Info", "Debug", "Log", "Warn", "Error"}) do
+        imgui.SameLine()
+        if imgui.Checkbox(v, self.logs_filter_toggles[k]) then
+            self.logs_filter_toggles[k] = not self.logs_filter_toggles[k]
+        end
+    end
+
+
+    imgui.BeginChild("Console log", 0,-imgui.GetItemsLineHeightWithSpacing())
+
+        for k,l in ipairs(self.logs) do
+            local color = 0xFFFFFF
+            if self.logs_filter_toggles[l.level]  then
+                if l.level == 1 then
+                    imgui.PushStyleColor("Text", 0.6,0.6,0.6,1)
+                elseif l.level == 2 then
+                    imgui.PushStyleColor("Text", 0.4,0.4,0.6,1)
+                elseif l.level == 3 then
+                    imgui.PushStyleColor("Text", 1,1,1,1)
+                elseif l.level == 4 then
+                    imgui.PushStyleColor("Text", 1,1,0,1)
+                elseif l.level == 5 then
+                    imgui.PushStyleColor("Text", 1,0.2,0.2,1)
+                end
+                imgui.TextUnformatted(l.msg)
+                imgui.PopStyleColor(1)
+            end
+        end
+        if self.scroll_to_bottom then
+            imgui.SetScrollHere();
+            self.scroll_to_bottom = false;
+        end
+    imgui.EndChild()
+
+
+    local status = false
+    imgui.PushItemWidth(imgui.GetContentRegionAvailWidth())
+    status, self.input_text = imgui.InputText("", self.input_text, 256, {"EnterReturnsTrue"})
+    imgui.PopItemWidth()
+
+    if self.take_focus then
+        imgui.SetKeyboardFocusHere(0)
+        self.take_focus = false
+    end
+
+    if status then
+        self:enterCommand()
+    end
+    imgui.End()
+end
+
+function Console:enterCommand()
+    if self.input_text == "" then return end
+    local command_line = self.input_text
+    local parts = splitArgs(command_line)
+    local args = {}
+    for i=2,#parts do
+        args[i-1] = parts[i]
+    end
+    self:log("> "..self.input_text, 1)
+    self:processCommand(parts[1]:lower(), args)
+    self.input_text = ""
+    self.take_focus = true
+end
+
+function Console:toggle()
+    DebugWindow.toggle(self)
+    if self.visible then
+        self.take_focus = true
+    end
+end
+
+function Console:log(str, level)
+    self.logs[#self.logs + 1] = {
+        msg = str,
+        level = level or 3
+    }
+    self.scroll_to_bottom = true
+end
+
+function Console:registerCommand(name, fun, help)
+    self.commands[name] = fun
+    if help then
+        self.command_helps[name] = help
+    end
+end
+
+function Console:processCommand(fun_name, args)
+    local command = self.commands[fun_name]
+    if not command then
+        self:log("Command not found : "..fun_name, ERROR)
+        return
+    end
+
+    local processed_commands = {}
+    local f = function()
+        command(unpack(args))
+    end
+    local success, result = xpcall(f, function(err) return debug.traceback(err) end)
+    if not success then
+        self:log(result, ERROR)
+    end
+end
+
+return Console

+ 17 - 0
debug_overlay/debugwindow.lua

@@ -0,0 +1,17 @@
+local class, object =  require("class"), require("class.object")
+
+local DebugWindow = class(object)
+
+function DebugWindow:init()
+    self.visible = false
+    self.shortcut = false
+end
+
+function DebugWindow:toggle()
+    self.visible = not self.visible
+end
+
+function DebugWindow:render()
+end
+
+return DebugWindow

+ 44 - 0
debug_overlay/ecs/init.lua

@@ -0,0 +1,44 @@
+local class =  require("class")
+local DebugWindow = require("debug_overlay.debugwindow")
+local ECS = require("ecs.ECS")
+require "imgui"
+
+local ECSDebug = class(DebugWindow)
+
+function ECSDebug:init()
+    self.ecs = ecs
+    self.once = true
+end
+
+function ECSDebug:render()
+    imgui.Begin("ECS")
+    imgui.BeginChild("Entity table", 0,-imgui.GetItemsLineHeightWithSpacing()*2)
+        imgui.Columns(ecs.n_components+1)
+        imgui.SetColumnOffset(1, 64)
+        -- Header
+        imgui.TextUnformatted("Entities")
+        imgui.NextColumn()
+        for i,v in pairs(ecs.components) do
+            imgui.TextUnformatted(string.format("%s", i))
+            imgui.NextColumn()
+        end
+        imgui.Separator()
+        for i=0,ECS.N_ENTITIES-1 do
+            if ecs.entities[i] then
+                imgui.TextUnformatted(string.format("%d", i))
+                imgui.NextColumn()
+                for j,c in pairs(ecs.components) do
+                    if c[i]._alive then
+                        imgui.TextUnformatted(tostring(c[i]))
+                    else
+                        imgui.NewLine()
+                    end
+                    imgui.NextColumn()
+                end
+            end
+        end
+    imgui.EndChild()
+    imgui.End()
+end
+
+return ECSDebug

+ 48 - 0
debug_overlay/graphs/init.lua

@@ -0,0 +1,48 @@
+local class =  require("class")
+local DebugWindow = require("debug_overlay.debugwindow")
+require "imgui"
+
+local Graphs = class(DebugWindow)
+
+local function shift_push_array(arr, new)
+    for i=1,#arr-1 do
+        arr[i] = arr[i+1]
+    end
+    arr[#arr] = new
+end
+
+function Graphs:init()
+    self.shortcut = "f11"
+    self.fps = {}
+    self.cpu = {}
+    self.mem = {}
+    self.mem_avg = 0
+    for i=1,60 do self.fps[i], self.cpu[i], self.mem[i] = 0,0,0 end
+end
+
+function Graphs:update_avg()
+    local sum = 0
+    for k,v in ipairs(self.mem) do
+        sum = sum + v
+    end
+    self.mem_avg = sum / #self.mem
+end
+
+function Graphs:update()
+    shift_push_array(self.fps, love.timer.getFPS())
+    shift_push_array(self.cpu, love.timer.getDelta())
+    shift_push_array(self.mem, collectgarbage("count"))
+    self:update_avg()
+end
+
+function Graphs:render()
+    imgui.Begin("Performance")
+    imgui.PlotLines("CPU", self.cpu, #self.cpu, 0, "", 0, 15)
+    imgui.PlotLines("FPS", self.fps, #self.fps, 0, "", 0, 60)
+    imgui.PlotLines("Mem", self.mem, #self.mem)
+    imgui.TextUnformatted(string.format("Average DT %0.3f ms", love.timer.getAverageDelta()*1000))
+    imgui.TextUnformatted(string.format("Memory %0.2f KB", self.mem_avg))
+    imgui.End()
+end
+
+return Graphs

+ 52 - 0
debug_overlay/init.lua

@@ -0,0 +1,52 @@
+local class, object =  require("class"), require("class.object")
+local Console = require("debug_overlay.console")
+local Graphs = require("debug_overlay.graphs")
+local Watch = require("debug_overlay.watch")
+local ECSDebug = require("debug_overlay.ecs")
+
+require("imgui")
+
+local Debug = class(object)
+
+function Debug:init()
+    self.debug_windows = {
+        Console = Console:new(),
+        Graphs = Graphs:new(),
+        Watch = Watch:new(),
+        ECS = ECSDebug:new(),
+    }
+    self.console = self.debug_windows.Console
+    self.watch = self.debug_windows.Watch
+end
+
+function Debug:render()
+    self.debug_windows.Graphs:update()
+    -- Menu
+    if imgui.BeginMainMenuBar() then
+        if imgui.BeginMenu("Debug") then
+            for k,v in pairs(self.debug_windows) do
+                if imgui.MenuItem(k, v.shortcut, v.visible) then
+                    v:toggle()
+                end
+            end
+            imgui.EndMenu()
+        end
+        imgui.EndMainMenuBar()
+    end
+    for k,v in pairs(self.debug_windows) do
+        if v.visible then
+            v:render()
+        end
+    end
+
+end
+
+function Debug:keypressed(key)
+    for k,v in pairs(self.debug_windows) do
+        if v.shortcut and key == v.shortcut then
+            v:toggle()
+        end
+    end
+end
+
+return Debug

+ 46 - 0
debug_overlay/watch/init.lua

@@ -0,0 +1,46 @@
+local class =  require("class")
+local DebugWindow = require("debug_overlay.debugwindow")
+
+require "imgui"
+
+local Watcher = class(DebugWindow)
+
+function Watcher:init( )
+    self.watched_values = {}
+    self.shortcut = "f10"
+end
+
+function Watcher:add(obj, member, name)
+    self.watched_values[#self.watched_values + 1] = setmetatable({
+        obj = obj,
+        member = member,
+        name = name or member
+    }, {__mode = 'v'})
+end
+
+function Watcher:render()
+    imgui.Begin("Watch")
+    if #self.watched_values > 0 then
+        imgui.Columns(2)
+        for k,v in ipairs(self.watched_values) do
+            if v.obj and v.member then
+                imgui.TextUnformatted(v.name)
+                imgui.NextColumn()
+                imgui.TextUnformatted(tostring(v.obj[v.member]))
+                imgui.NextColumn()
+            elseif v.obj == nil then
+                imgui.TextUnformatted(v.name .. " <<Miss. obj.>>")
+                imgui.NextColumn()
+                imgui.NextColumn()
+            else
+                imgui.TextUnformatted(v.name .. " <<Miss. mem.>>")
+                imgui.NextColumn()
+                imgui.NextColumn()
+
+            end
+        end
+    end
+    imgui.End()
+end
+
+return Watcher

+ 19 - 0
ecs/Component.lua

@@ -0,0 +1,19 @@
+local ffi = require("ffi")
+local class, object =  require("class"), require("class.object")
+require("ecs")
+
+local Component = class(object)
+
+local function make_component(name, struct)
+    if type(struct) == "string" then
+        -- Assuming FFI cdef
+        -- TODO : find a way to merge class and userdata
+        local ct = ffi.cdef("typdef struct {"..struct.."} "..name..";")
+        return ffi.metatype(ct, class(Component))
+    elseif struct.instanceof and struct:instanceof(Component) then
+        -- Assuming parent class
+        return class(struct)
+    else
+        return class(Component)
+    end
+end

+ 14 - 0
ecs/ECS.lua

@@ -0,0 +1,14 @@
+local ffi = require("ffi")
+local class, object =  require("class"), require("class.object")
+require("ecs")
+
+local ECS = class(object)
+ECS.N_ENTITIES = 100
+function ECS:init()
+    self.entities = ffi.new("entity_t[?]", ECS.N_ENTITIES)
+    self.components = {}
+    self.components["test_t"] = ffi.new("test_t[?]", ECS.N_ENTITIES)
+    self.n_components = 1
+end
+
+return ECS

+ 14 - 0
ecs/init.lua

@@ -0,0 +1,14 @@
+local ffi = require("ffi")
+
+ffi.cdef[[
+    void free(void *ptr);
+    typedef uint32_t entity_id_t;
+    typedef bool entity_t;
+
+    typedef struct {
+        bool _alive;
+        int x;
+        int y;
+    } test_t;
+]]
+

+ 99 - 0
main.lua

@@ -0,0 +1,99 @@
+require "imgui"
+local ECS = require("ecs.ECS")
+local Debug = require("debug_overlay")
+local inspect = require 'inspect'
+
+debug_overlay = Debug:new()
+local ecs = ECS:new()
+local a = {b = 42}
+_G.debug_overlay = debug_overlay
+_G.ecs = ecs
+
+--
+-- LOVE callbacks
+--
+function love.load(arg)
+    for i=0,100-1 do
+        ecs.entities[i] = false
+    end
+    ecs.entities[0] = true
+    ecs.components.test_t[0]._alive = true
+
+
+end
+
+function love.update(dt)
+    imgui.NewFrame()
+end
+
+function love.draw()
+    debug_overlay:render()
+    imgui.Render();
+end
+
+function love.quit()
+    imgui.ShutDown();
+end
+
+--
+-- User inputs
+--
+function love.textinput(t)
+    imgui.TextInput(t)
+    if not imgui.GetWantCaptureKeyboard() then
+        -- Pass event to the game
+    end
+end
+
+function love.resize(width, height)
+    pixel = shine.pixelate()
+    crt = shine.crt()
+    glow = shine.glowsimple()
+    blur = shine.boxblur()
+    scan = shine.scanlines()
+    pe = scan:chain(glow):chain(crt)
+end
+
+function love.keypressed(key)
+    imgui.KeyPressed(key)
+    debug_overlay:keypressed(key)
+    if not imgui.GetWantCaptureKeyboard() then
+        -- Pass event to the game
+    end
+end
+
+function love.keyreleased(key)
+    imgui.KeyReleased(key)
+    if not imgui.GetWantCaptureKeyboard() then
+        -- Pass event to the game
+    end
+    a = nil
+end
+
+function love.mousemoved(x, y)
+    imgui.MouseMoved(x, y)
+    if not imgui.GetWantCaptureMouse() then
+        -- Pass event to the game
+    end
+end
+
+function love.mousepressed(x, y, button)
+    imgui.MousePressed(button)
+    if not imgui.GetWantCaptureMouse() then
+        -- Pass event to the game
+    end
+end
+
+function love.mousereleased(x, y, button)
+    imgui.MouseReleased(button)
+    if not imgui.GetWantCaptureMouse() then
+        -- Pass event to the game
+    end
+end
+
+function love.wheelmoved(x, y)
+    imgui.WheelMoved(y)
+    if not imgui.GetWantCaptureMouse() then
+        -- Pass event to the game
+    end
+end

+ 5 - 0
utils.lua

@@ -0,0 +1,5 @@
+function math.clamp(val, lower, upper)
+    assert(val and lower and upper, "not very useful error message here")
+    if lower > upper then lower, upper = upper, lower end -- swap if boundaries supplied the wrong way
+    return math.max(lower, math.min(upper, val))
+end