vision/vendor/lily_thread.lua
2022-10-13 16:06:52 +02:00

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