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.

asked Feb 19, 2016 at 1:34
\$\endgroup\$
2
  • \$\begingroup\$ 1. goto is evil. 2. Use better variable names than e or r. \$\endgroup\$ Commented Feb 22, 2016 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\$ Commented Apr 17, 2016 at 16:40

1 Answer 1

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
200_success
145k22 gold badges190 silver badges478 bronze badges
answered Apr 17, 2016 at 16:29
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.