242 lines
6.7 KiB
Lua
242 lines
6.7 KiB
Lua
-- LOVE Asset Async Loader (Thread Part)
|
|
-- Copyright (c) 2021 Miku AuahDark
|
|
--
|
|
-- This software is provided 'as-is', without any express or implied
|
|
-- warranty. In no event will the authors be held liable for any damages
|
|
-- arising from the use of this software.
|
|
--
|
|
-- Permission is granted to anyone to use this software for any purpose,
|
|
-- including commercial applications, and to alter it and redistribute it
|
|
-- freely, subject to the following restrictions:
|
|
--
|
|
-- 1. The origin of this software must not be misrepresented; you must not
|
|
-- claim that you wrote the original software. If you use this software
|
|
-- in a product, an acknowledgment in the product documentation would be
|
|
-- appreciated but is not required.
|
|
-- 2. Altered source versions must be plainly marked as such, and must not be
|
|
-- misrepresented as being the original software.
|
|
-- 3. This notice may not be removed or altered from any source distribution.
|
|
|
|
-- Task channel structure (inside table)
|
|
-- request ID (string) or error channel to signal quit
|
|
-- Task name (string)
|
|
-- Amount of arguments (number)
|
|
-- n-amount of arguments (Variant)
|
|
|
|
-- Load LOVE module
|
|
local love = require("love")
|
|
require("love.event")
|
|
require("love.data")
|
|
-- Non-thread-safe modules
|
|
local ntsModules = {"graphics", "window"}
|
|
-- But love.graphics must be treated specially
|
|
local hasGraphics = false
|
|
|
|
-- See lily.lua initThreads() function for more information about
|
|
-- arguments passed
|
|
local modules, errorChannel, taskChannel, dataPullChannel, updateModeChannel = ...
|
|
|
|
-- Load modules
|
|
for _, v in pairs(modules) do
|
|
local f = false
|
|
if v == "graphics" then hasGraphics = true end
|
|
|
|
for i = 1, #ntsModules do
|
|
if ntsModules[i] == v then
|
|
f = true
|
|
break
|
|
end
|
|
end
|
|
|
|
if not(f) then
|
|
require("love."..v)
|
|
end
|
|
end
|
|
|
|
-- Handlers
|
|
local lilyProcessor = {}
|
|
local getUpdateMode
|
|
do
|
|
local function atomicFuncGetUpdateMode()
|
|
return updateModeChannel:peek()
|
|
end
|
|
function getUpdateMode()
|
|
return updateModeChannel:performAtomic(atomicFuncGetUpdateMode)
|
|
end
|
|
end
|
|
|
|
-- Function to push data
|
|
local function pushData(reqID, v1, v2)
|
|
local updateMode = getUpdateMode()
|
|
if updateMode == "automatic" then
|
|
-- Event push
|
|
love.event.push("lily_resp", reqID, v1, v2)
|
|
elseif updateMode == "manual" then
|
|
-- Channel push
|
|
dataPullChannel:push({reqID, v1, v2})
|
|
end
|
|
end
|
|
|
|
-- Macro function to create handler
|
|
local function lilyHandlerFunc(reqtype, minarg, handler)
|
|
lilyProcessor[reqtype] = {minarg = minarg, handler = handler}
|
|
end
|
|
|
|
|
|
if love.audio then
|
|
lilyHandlerFunc("newSource", 2, function(t)
|
|
return love.audio.newSource(t[1], t[2])
|
|
end)
|
|
end
|
|
|
|
-- Always exist
|
|
if love.data then
|
|
local function isCompressedData(t)
|
|
return type(t) == "userdata" and t:typeOf("CompressedData")
|
|
end
|
|
|
|
lilyHandlerFunc("compress", 1, function(t)
|
|
return love.data.compress("data", t[1] or "lz4", t[2], t[3])
|
|
end)
|
|
lilyHandlerFunc("decompress", 1, function(t)
|
|
if type(t[2]) == "userdata" and t[2]:typeOf("Data") and love._version < "11.2" then
|
|
-- Prior to LOVE 11.2, love.data.decompress can't decompress
|
|
-- Data object (not CompressedData) due to bug in the code
|
|
-- when handling this variant. So, convert it to string before
|
|
-- passing it.
|
|
t[2] = t[2]:getString()
|
|
end
|
|
return love.data.decompress("data", t[1], t[2])
|
|
end)
|
|
end
|
|
|
|
-- Always exist
|
|
if love.filesystem then
|
|
lilyHandlerFunc("append", 2, function(t)
|
|
return assert(love.filesystem.append(t[1], t[2], t[3]))
|
|
end)
|
|
lilyHandlerFunc("newFileData", 1, function(t)
|
|
return assert(love.filesystem.newFileData(t[1], t[2], t[3]))
|
|
end)
|
|
lilyHandlerFunc("read", 1, function(t)
|
|
return assert(love.filesystem.read(t[1], t[2]))
|
|
end)
|
|
lilyHandlerFunc("readFile", 1, function(t)
|
|
return t[1].read(t[1], t[2])
|
|
end)
|
|
lilyHandlerFunc("write", 2, function(t)
|
|
return assert(love.filesystem.write(t[1], t[2], t[3]))
|
|
end)
|
|
lilyHandlerFunc("writeFile", 2, function(t)
|
|
return t[1].write(t[1], t[2], t[3])
|
|
end)
|
|
end
|
|
|
|
if hasGraphics then
|
|
lilyHandlerFunc("newFont", 1, function(t)
|
|
return love.font.newRasterizer(t[1], t[2], t[3], t[4])
|
|
end)
|
|
lilyHandlerFunc("newImage", 1, function(t)
|
|
local s, x = pcall(love.image.newImageData, t[1])
|
|
return (s and x or love.image.newCompressedData(t[1])), select(2, unpack(t))
|
|
end)
|
|
lilyHandlerFunc("newVideo", 1, function(t)
|
|
return love.video.newVideoStream(t[1]), t[2]
|
|
end)
|
|
lilyHandlerFunc("newCubeImage", 1, function(t)
|
|
-- If it's not table, then it should be processed with
|
|
-- love.image.newCubeFaces (undocumented function)
|
|
if type(t[1]) ~= "table" then
|
|
local id = t[1]
|
|
if type(id) ~= "userdata" or id:type() ~= "ImageData" then
|
|
id = love.image.newImageData(id)
|
|
end
|
|
t[1] = {love.image.newCubeFaces(id)}
|
|
end
|
|
for i = 1, 6 do
|
|
local v = t[1][i]
|
|
local t = v:type()
|
|
if t ~= "userdata" or (t ~= "ImageData" and t ~= "CompressedImageData") then
|
|
t[1][i] = love.image.newImageData(v)
|
|
end
|
|
end
|
|
return t[1], t[2]
|
|
end)
|
|
end
|
|
|
|
if love.image then
|
|
lilyHandlerFunc("encodeImageData", 1, function(t)
|
|
return t[1].encode(t[1], t[2])
|
|
end)
|
|
lilyHandlerFunc("newImageData", 1, function(t)
|
|
return love.image.newImageData(t[1])
|
|
end)
|
|
lilyHandlerFunc("newCompressedData", 1, function(t)
|
|
return love.image.newCompressedData(t[1])
|
|
end)
|
|
lilyHandlerFunc("pasteImageData", 7, function(t)
|
|
return t[1].paste(t[1], t[2], t[3], t[4], t[5], t[6], t[7])
|
|
end)
|
|
end
|
|
|
|
if love.sound then
|
|
lilyHandlerFunc("newSoundData", 1, function(t)
|
|
return love.sound.newSoundData(t[1], t[2], t[3], t[4])
|
|
end)
|
|
end
|
|
|
|
if love.video then
|
|
lilyHandlerFunc("newVideoStream", 1, function(t)
|
|
return love.video.newVideoStream(t[1])
|
|
end)
|
|
end
|
|
|
|
local handlerFunc
|
|
local handlerArg
|
|
local function callHandler()
|
|
return handlerFunc(handlerArg)
|
|
end
|
|
|
|
-- Main loop
|
|
while true do
|
|
collectgarbage()
|
|
collectgarbage()
|
|
-- Get request (see the beginning of file for table format)
|
|
local request = taskChannel:demand()
|
|
-- If it's not table then quit signaled
|
|
if type(request) ~= "table" then return end
|
|
local tasktype = request[2]
|
|
|
|
-- If it's exist in lilyProcessor table that means it's valid event
|
|
if lilyProcessor[tasktype] then
|
|
local task = lilyProcessor[tasktype]
|
|
local argc = request[3]
|
|
-- Check minarg count
|
|
if argc < task.minarg then
|
|
-- Too few arguments
|
|
pushData(request[1], errorChannel, string.format(
|
|
"'%s': too few arguments (at least %d is required)",
|
|
tasktype,
|
|
task.minarg
|
|
))
|
|
else
|
|
local argv = {}
|
|
-- Get arguments
|
|
for i = 1, argc do
|
|
argv[i] = request[3 + i] -- 4th and later are arguments
|
|
end
|
|
|
|
-- Call
|
|
handlerFunc = task.handler
|
|
handlerArg = argv
|
|
local result = {xpcall(callHandler, debug.traceback)}
|
|
if result[1] == false then
|
|
-- Error
|
|
pushData(request[1], errorChannel, string.format("'%s': %s", tasktype, result[2]))
|
|
end
|
|
-- Push data (v1 is true, v2 is return values)
|
|
table.remove(result, 1)
|
|
pushData(request[1], true, result)
|
|
end
|
|
end
|
|
end
|