7
\$\begingroup\$

I made this resource queue in Love2D.

function GameState:queue(file, arg)
    self.res.loaded = self.res.loaded + 1;
    self.res.queue[self.res.loaded] = {f = file, t = game.getResourceType(file), id = self.res.loaded, s = arg};
    game.log("Resource Queued: "..file);
    return {f = self.res.loaded, t = game.getResourceType(file), id = self.res.loaded, s = arg};
end

function GameState:loadQueue(loader, post)
    self.post = post or function() self.state = self.prevState end;

    game.log("Loading Resource Queue...");

    queueThread = love.thread.newThread("queue.lua");
    queueChannel = love.thread.getChannel("queue");

    local fonts = {};

    for k, v in pairs(self.res.queue) do
        if (not v or not v.f or not v.t) then goto continue end;
        if (v.t ~= self.RESOURCE_FONT) then
            queueChannel:push(v);
        else
            fonts[k] = v;
        end
        ::continue::
    end

    for k, v in pairs(fonts) do
        self.res.fnt[k] = love.graphics.newFont(v.f, v.s);
        print("Loaded Resource: "..v.f);
    end

    queueChannel:push(table.size(self.res.queue)-table.size(fonts));

    if (table.size(self.res.queue)-table.size(fonts) > 0) then
        queueThread:start();
    else
        queueChannel = nil;
        queueThread = nil;
        return;
    end

    self.state = self.STATE_LOADING
end

The thread:

require "love.image"
require "love.audio"
require "love.sound"
require "love.timer"

queue = {};
img = {};
snd = {};
local queueChannel = love.thread.getChannel("queue");

local pop = queueChannel:pop();

while (type(pop) ~= "number" and pop) do
    queue[pop.id] = pop;
    pop = queueChannel:pop();
end

for k, v in pairs(queue) do
    if (not v or not v.f or not v.t) then print("Resource Load Failed!"); goto continue; end;

    if (v.t == 0) then -- image
        img[k] = love.image.newImageData(v.f);
        queueChannel:supply({data = img[k], t = 0, id = k, s = v.s});
    end
    if (v.t == 1) then -- sound
        snd[k] = love.audio.newSource(v.f, "static");
        queueChannel:supply({data = snd[k], t = 1, id = k, s = v.s});
    end
    ::continue::
    queue[k] = nil;
    print("Loaded Resource: "..v.f);
    love.timer.sleep(0.01);
end

queueChannel:clear();
queueChannel:push(true);

return;

Handling while loading resources:

if (self.state == self.STATE_LOADING) then
    local e = queueThread:getError();
    if (e ~= nil) then
        print("Thread: "..e)
    end

    local r = queueChannel:peek();
    if (type(r) == "table" and r and r.data and r.t) then
        if (r.t == self.RESOURCE_IMAGE) then
            if (r.s) then
                love.graphics.setDefaultFilter(r.s, r.s);
            end
            self.res.img[r.id] = love.graphics.newImage(r.data);
        end
        if (r.t == self.RESOURCE_SOUND) then
            self.res.snd[r.id] = r.data;
        end
        queueChannel:pop();
    end

    if (queueChannel:peek() == true and type(queueChannel:peek()) == "boolean") then
        self.post();
        game.log("Finished Loading Resources.");
    end
end

If there's any bad habits, or anything that could be improved, please let me know.

\$\endgroup\$
  • \$\begingroup\$ 1. goto is evil. 2. Use better variable names than e or r. \$\endgroup\$ – hjpotter92 Feb 22 '16 at 12:07
  • 1
    \$\begingroup\$ I disagree. It's legitimate to use goto in some cases, for instance when jumping to a point within the same function goto is being used. goto is used a lot in huge codebases such as the Linux kernel. This goto is evil thing is cargocult originated by Dijkstra's infamous article Goto statement considered harmful. In this particular case, goto is well used. It's the recommended way of implementing continue in Lua, as the language lacks a native continue statement. \$\endgroup\$ – Diego Pino Apr 17 '16 at 16:40
1
\$\begingroup\$

In Lua, it's not necessary to separate statements by a semicolon (it's not even necessary to do it when writting several statements in the same line). However it's possible to do it.

In this snippet:

for k, v in pairs(self.res.queue) do
    if (not v or not v.f or not v.t) then goto continue end;
    if (v.t ~= self.RESOURCE_FONT) then
        queueChannel:push(v);
    else
        fonts[k] = v;
    end
    ::continue::
end

You're using pairs in several parts of the code to iterate tables which are actually arrays. Variable self.res.queue is an array since:

function GameState:queue(file, arg)
    self.res.loaded = self.res.loaded + 1;
    self.res.queue[self.res.loaded] = <object>;
end

In the cases a table is actually an array is preferred to use ipairs because is way more faster than pairs. In addition to that, it tells other people reading the code the table is actually an array (which is a valuable hint in a dynamic language such as Lua).

At the bottom of GameState:loadQueue:

if (table.size(self.res.queue)-table.size(fonts) > 0) then
    queueThread:start(); else
    queueChannel = nil;
    queueThread = nil;
    return;
end

self.state = self.STATE_LOADING

The return statement in the else branch can be removed if you move self.state up to the then branch. The semantics hold as either queueThread:start() or not.

if (table.size(self.res.queue)-table.size(fonts) > 0) then
    self.state = self.STATE_LOADING;
    queueThread:start();
else
    queueChannel = nil;
    queueThread = nil;
    return;
end
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.