Custom class crashes programm because of.... I dont know

Started by Terandox, May 12, 2022, 08:24 AM

Previous topic - Next topic

Terandox

Hello,

I tried to create a game-engine library using my self created class library.
I had to create my own class library, because the common way of using setmetatable is not working.

Class Library:
local class = {}

function class:new(value)
    value = value or {}
    local new_class = {}
    for cindex, cdata in pairs(self) do
        if type(cdata) == "table" then
            new_class[cindex] = self:t_rebuild(cdata)
        else
            new_class[cindex] = cdata
        end
    end
    for vindex, vdata in pairs(value) do
        if type(vdata) == "table" then
            new_class[vindex] = self:t_rebuild(vdata)
        else
            new_class[vindex] = vdata
        end
    end
    return new_class
end

function class:destroy()
    self = nil
end

function class:t_rebuild(my_table)
    my_table = my_table or {}
    local new_table = {}
    for index, data in pairs(my_table) do
        if type(data) == "table" then
            new_table[index] = self:t_rebuild(data)
        else
            new_table[index] = data
        end
    end
    return new_table
end

return { class = class }

Game-engine Library:
if fs.exists("class") == false then shell.run("wget https://raw.githubusercontent.com/Terandox-The-Pineapple/TRX-Librarys/main/class.lua class") end
local class_lib = require("class")

local players = {}
local enemys = {}
local others = {}
local backgrounds = {}
backgrounds.backgroundlist = {}
backgrounds.selected = 1
local menus = {}
menus.menulist = {}
menus.selected = 1
local controllers = {}
local render = {}

function render:init()
    for x = 1, 51, 1 do
        if self[x] == nil then self[x] = {} end
        for y = 1, 19, 1 do
            if self[x][y] == nil then self[x][y] = {} end
            self[x][y].color = false
            self[x][y].text = false
        end
    end
end

render = class_lib.class:new(render)

local entity = { posX = 1, posY = 1, render = false }

function entity:getSize()
    local sizeX = 0
    local sizeY = 0
    for x = 1, 51, 1 do
        local countY = 0
        for y = 1, 19.1 do
            local countX = 0
            if self.render[x][y].color ~= false or self.render[x][y].text ~= false then countY = countY + 1 end
            for inner_x = 1, 51, 1 do
                if self.render[inner_x][y].color ~= false or self.render[inner_x][y].text ~= false then countX = countX + 1 end
            end
            if countX > sizeX then sizeX = countX end
        end
        if countY > sizeY then sizeY = countY end
    end
    return sizeX, sizeY
end

function entity:moveRight()
    local sizeX, sizeY = self:getSize()
    if self.posX <= (51 - sizeX) then self.posX = self.posX + 1 end
end

function entity:moveLeft()
    if self.posX > 1 then self.posX = self.posX - 1 end
end

function entity:moveUp()
    if self.posY > 1 then self.posY = self.posY - 1 end
end

function entity:moveDown()
    local sizeX, sizeY = self:getSize()
    if self.posY <= (19 - sizeY) then self.posY = self.posY + 1 end
end

function entity:getLastPosition()
    local sizeX, sizeY = self:getSize()
    return self.posX + (sizeX - 1), self.posY + (sizeY - 1)
end

function entity:draw()
    local sizeX, sizeY = self:getSize()
    for x = 1, sizeX, 1 do
        for y = 1, sizeY, 1 do
            if self.render[x][y].color ~= false or self.render[x][y].text ~= false then
                term.setCursorPos(x + (self.posX - 1), y + (self.posY - 1))
                if self.render[x][y].color ~= false then
                    term.setBackgroundColor(self.render[x][y].color)
                else
                    term.setBackgroundColor(backgrounds.backgroundlist[backgrounds.selected].render[x + (self.posX - 1)][y + (self.posY - 1)].color)
                end
                if self.render[x][y].text ~= false then
                    term.write(self.render[x][y].text)
                else
                    term.write(" ")
                end
            end
        end
    end
end

function entity:undraw()
    local sizeX, sizeY = self:getSize()
    for x = 1, sizeX, 1 do
        for y = 1, sizeY, 1 do
            if self.render[x][y].color ~= false or self.render[x][y].text ~= false then
                term.setCursorPos(x + (self.posX - 1), y + (self.posY - 1))
                term.setBackgroundColor(backgrounds.backgroundlist[backgrounds.selected].render[x + (self.posX - 1)][y + (self.posY - 1)].color)
                term.write(" ")
            end
        end
    end
end

function entity:kill()
    self:undraw()
    t_removeItem(self.parent, self)
    self:destroy()
end

function entity:collision(target)
    local lastX, lastY = self:getLastPosition()
    local tLastX, tLastY = target:getLastPosition()
    local sizeX, sizeY = self:getSize()
    local tSizeX, tSizeY = target:getSize()
    for x = self.posX, lastX, 1 do
        for y = self.posY, lastY, 1 do
            for tx = target.posX, tLastX, 1 do
                for ty = target.posY, tLastY, 1 do
                    if x == tx and y == ty and (self.render[x - (self.posX - 1)][y - (self.posY - 1)].color ~= false or self.render[x - (self.posX - 1)][y - (self.posY - 1)].text ~= false) and (target.render[tx - (target.posX - 1)][ty - (target.posY - 1)].color ~= false or target.render[tx - (target.posX - 1)][ty - (target.posY - 1)].text ~= false) then
                        return true
                    end
                end
            end
        end
    end
end

entity = class_lib.class:new(entity)

local background = { render = false }

function background:draw()
    for x = 1, 51, 1 do
        for y = 1, 19, 1 do
            term.setCursorPos(x, y)
            if self.render[x][y].color ~= false then
                term.setBackgroundColor(self.render[x][y].color)
            else
                term.setBackgroundColor(colours.black)
            end
            if self.render[x][y].text ~= false then
                term.write(self.render[x][y].text)
            else
                term.write(" ")
            end
        end
    end
end

background = class_lib.class:new(background)

local menu = { selection = {}, selected = 1 }

function menu:draw(posX, posY)
    local my_posY, my_posX = posY, posX
    for index, point in pairs(self.selection) do
        term.setCursorPos(my_posX, my_posY)
        if point[1][1].color ~= false then
            term.setBackgroundColor(point[1][1].color)
        else
            term.setBackgroundColor(backgrounds.backgroundlist[backgrounds.selected].render[my_posX][my_posY].color)
        end
        if index == self.selected then
            term.setTextColor(colours.green)
        end
        if point[1][1].text ~= false then
            term.write(point[1][1].text)
        else
            term.write("Default Text")
        end
        term.setTextColor(colours.black)
        my_posY = my_posY + 2
    end
end

function menu:up()
    if self.selected > 1 then
        self.selected = self.selected - 1
    else
        self.selected = #self.selection
    end
end

function menu:down()
    if self.selected < #self.selection then
        self.selected = self.selected + 1
    else
        self.selected = 1
    end
end

menu = class_lib.class:new(menu)

local controller = { finished = false, keys = {} }

function controller:start(p_func)
    self.finished = false
    while self.finished == false do
        parallel.waitForAny(function() self:get_key() end, p_func)
    end
end

function controller:get_key()
    while self.finished == false do
        local _, key = os.pullEvent("key")
        for index, data in pairs(self.keys) do
            if data.key == key then
                data.func()
            end
        end
    end
end

function controller:stop()
    self.finished = true
end

controller = class_lib.class:new(controller)

function t_getIndex(table, item)
    for index, value in pairs(table) do
        if item == value then
            return index
        end
    end
    return false
end

function t_removeIndex(table, index)
    local new_table = {}
    for i, value in pairs(table) do
        if i ~= index then
            if type(i) == "number" then
                new_table[#new_table + 1] = table[i]
            else
                new_table[i] = table[i]
            end
        end
    end
    table = new_table
    return table
end

function t_removeItem(table, item)
    local index = t_getIndex(table, item)
    return t_removeIndex(table, index)
end

function add_player(posX, posY, name)
    players[name] = entity:new({ posX = posX, posY = posY, render = render:new(), parent = players })
    players[name].render:init()
    return players[name]
end

function add_enemy(posX, posY, name)
    enemys[name] = entity:new({ posX = posX, posY = posY, render = render:new(), parent = enemys })
    enemys[name].render:init()
    return enemys[name]
end

function add_other(posX, posY, name)
    others[name] = entity:new({ posX = posX, posY = posY, render = render:new(), parent = others })
    others[name].render:init()
    return others[name]
end

function add_background(name)
    backgrounds.backgroundlist[name] = background:new({ render = render:new() })
    backgrounds.backgroundlist[name].render:init()
    return backgrounds.backgroundlist[name]
end

function add_menu(name)
    menus.menulist[name] = menu:new()
    return menus.menulist[name]
end

function add_menu_point(target, text, color)
    local n_render = render:new()
    n_render:init()
    local index = t_getIndex(menus.menulist, target)
    n_render[1][1].text = text
    if color ~= nil then
        n_render[1][1].color = color
    end
    menus.menulist[index].selection[#menus.menulist[index].selection + 1] = n_render
    return menus.menulist[index].selection[#menus.menulist[index].selection]
end

function change_background(target)
    local index = t_getIndex(backgrounds.backgroundlist, target)
    backgrounds.selected = index
    backgrounds.backgroundlist[index]:draw()
end

function change_menu(target, posX, posY)
    local index = t_getIndex(menus.menulist, target)
    menus.selected = index
    menus.menulist[index]:draw(posX, posY)
end

function add_controller(name)
    controllers[name] = controller:new()
    return controllers[name]
end

function add_controller_key(target, name, key, func)
    local index = t_getIndex(controllers, target)
    controllers[index].keys[name] = { key = key, func = func }
    return controllers[index].keys[name]
end

return { t_getIndex = t_getIndex, t_removeIndex = t_removeIndex, t_removeItem = t_removeItem, add_player = add_player, add_enemy = add_enemy, add_other = add_other, add_background = add_background, add_menu = add_menu, add_menu_point = add_menu_point, change_background = change_background, change_menu = change_menu, add_controller = add_controller, add_controller_key = add_controller_key }

Test Programm:
if fs.exists("game-utils") == false then shell.run("wget https://raw.githubusercontent.com/Terandox-The-Pineapple/TRX-Librarys/dev/game-utils.lua game-utils") end
local game_utils = require("game-utils")
math.randomseed(os.time())
local my_back = game_utils.add_background("my_back")
for x = 1, 51, 1 do
    for y = 1, 19, 1 do
        if x == 1 or x == 51 or y == 1 or y == 19 then
            my_back.render[x][y].color = colours.green
        else
            my_back.render[x][y].color = colours.red
        end
    end
end
game_utils.change_background(my_back)
local player = game_utils.add_player(2,2,"player_1")
local enemy = game_utils.add_enemy(50,18,"enemy_1")
local buffer = false
player.render[2][1].color = colours.black
player.render[2][2].color = colours.black
player.render[2][3].color = colours.black
player.render[1][2].color = colours.black
player.render[3][2].color = colours.black
enemy.render[1][1].color = colours.pink
local controlls = game_utils.add_controller("controlls")
game_utils.add_controller_key(controlls, "Quit", keys.q, function()
    controlls:stop()
    term.setBackgroundColor(colors.black)
    shell.run("clear")
    print("Programm finished")
end)
game_utils.add_controller_key(controlls, "Up", keys.up, function()
    buffer = "U"
end)
game_utils.add_controller_key(controlls, "Down", keys.down, function()
    buffer = "D"
end)
game_utils.add_controller_key(controlls, "Left", keys.left, function()
    buffer = "L"
end)
game_utils.add_controller_key(controlls, "Right", keys.right, function()
    buffer = "R"
end)
local e_move = "L"
local bullets = {}
player:draw()
function game()
    os.sleep(0.01)
    if enemy:collision(player) then hit() end
    enemy:undraw()
    if e_move == "R" then
        enemy:moveRight()
        if enemy.posX >= 50 then
            e_move = "L"
        end
    elseif e_move == "L" then
        enemy:moveLeft()
        if enemy.posX <= 2 then
            e_move = "R"
        end
    else
        controlls:stop()
    end
    enemy:draw()
    if enemy:collision(player) then hit() end
    for index, bullet in pairs(bullets) do
        bullet:fly()
    end
    test_shot()
    local sizeX, sizeY = player:getSize()
    if buffer == "U" and player.posY > 2 then
        player:undraw()
        player:moveUp()
        player:draw()
    elseif buffer == "D" and player.posY < (19 - sizeY) then
        player:undraw()
        player:moveDown()
        player:draw()
    elseif buffer == "L" and player.posX > 2 then
        player:undraw()
        player:moveLeft()
        player:draw()
    elseif buffer == "R" and player.posX < (51 - sizeX) then
        player:undraw()
        player:moveRight()
        player:draw()
    end
end

function test_shot()
    local value = math.random(1,5)
    if value == 1 then shoot() end
end

function shoot()
    local bullet = game_utils.add_other(enemy.posX,enemy.posY-1, #bullets+1)

    function bullet:fly()
        if self.posY > 2 then
            if self:collision(player) then hit() end
            self:undraw()
            self:moveUp()
            self:draw()
            if self:collision(player) then hit() end
        else
            if self:collision(player) then hit() end
            game_utils.t_removeItem(bullets,self)
            self:kill()
        end
    end

    bullet.render[1][1].color = colours.purple

    bullets[#bullets+1] = bullet
end

function hit()
    controlls:stop()
    term.setBackgroundColor(colors.black)
    shell.run("clear")
    print("Hit")
    return true
end

controlls:start(game)

Link to Test Programm (Installs dependencys automatic on start): https://pastebin.com/y2GjKDJ7

The test programm runs normal at first and then gets slower each time a bullet is shot.
After a short time the programm than crashes with this error:
Quoteclass:32: To long without yielding

I have no idea what i did wrong and hope that someone can help me out.

Terandox

After a lot of headscratching i managed to locate the bug and removed it.
The problem was that render:init() cloned the entire render class 1 times inside the key it should be and 1 times outside of it.

Also i created all bullets with add_other() function. I now create 1 class with add_other() function and the rests out if this one with :new() function.

Finaly it works... hurray.

The final version of my test programm can be obtained with the link in the main post.
The final versions of my librarys can be obtained on my github.

wish you a nice day