601 lines
17 KiB
Lua
601 lines
17 KiB
Lua
local M = {}
|
|
|
|
M._actorsInitialized = false -- if true, no new actors can be created
|
|
M._actorsInitializing = false -- the above but a bit more explicit
|
|
|
|
local drawfunctionArguments = {}
|
|
local specialActorFrames = {} -- ones defined specifically; here for drawfunction jank
|
|
|
|
---@param frame ActorFrame
|
|
---@param func function
|
|
--- SetDrawFunction with special behavior to account for Uranium's actor loading scheme.
|
|
function setDrawFunction(frame, func)
|
|
--if not frame.__raw then error('uranium: cannot set actorframe drawfunction during module loadtime! put this in uranium.init or actor:addcommand(\'Init\', ...)', 2) end
|
|
if not frame.SetDrawFunction then error('uranium: expected an actorframe but got something that doesn\'t even bother to implement SetDrawFunction', 2) end
|
|
if type(func) ~= 'function' then error('uranium: tried to set a drawfunction to a.. ' .. type(func) .. '?? the hell', 2) end
|
|
frame:SetDrawFunction(function()
|
|
for i = 1, frame:GetNumChildren() do
|
|
local a = frame:GetChildAt(i - 1)
|
|
if specialActorFrames[a] == false then
|
|
a:Draw()
|
|
end
|
|
end
|
|
local args = drawfunctionArguments[frame]
|
|
if args then
|
|
func(unpack(args))
|
|
else
|
|
func()
|
|
end
|
|
end)
|
|
end
|
|
|
|
---@param actor Actor
|
|
---@param shader RageShaderProgram
|
|
function setShader(actor, shader)
|
|
if not shader.__raw then
|
|
uranium.on('init', function() setShader(actor, shader) end)
|
|
else
|
|
actor:SetShader(shader.__raw)
|
|
end
|
|
end
|
|
|
|
function setShaderfuck(shader)
|
|
if not shader.__raw then
|
|
uranium.on('init', function() setShaderfuck(shader) end)
|
|
else
|
|
DISPLAY:ShaderFuck(shader.__raw)
|
|
end
|
|
end
|
|
|
|
function clearShaderfuck()
|
|
DISPLAY:ClearShaderFuck()
|
|
end
|
|
|
|
oat._actorAssociationTable = {}
|
|
|
|
-- Gets every child of an ActorFrame. More accurate than :GetChildren()
|
|
---@param frame ActorFrame
|
|
---@return Actor[]
|
|
function getChildren(frame)
|
|
local c = oat._actorAssociationTable[frame]
|
|
if c then
|
|
return c
|
|
else
|
|
error('uranium: actorframe doesn\'t exist (or isn\'t an actorframe)', 2)
|
|
end
|
|
end
|
|
|
|
local patchedFunctions = {}
|
|
function oat._patchFunction(f, obj)
|
|
if not patchedFunctions[f] then patchedFunctions[f] = {} end
|
|
if not patchedFunctions[f][obj] then
|
|
patchedFunctions[f][obj] = function(...)
|
|
arg[1] = obj
|
|
local results
|
|
local status, result = pcall(function()
|
|
-- doing it this way instead of returning because lua
|
|
-- offers no way of grabbing everything BUT the first
|
|
-- argument out of pcall
|
|
results = {f(unpack(arg))}
|
|
end)
|
|
if not status then
|
|
error(result, 2)
|
|
else
|
|
return unpack(results)
|
|
end
|
|
end
|
|
end
|
|
return patchedFunctions[f][obj]
|
|
end
|
|
|
|
M._globalQueue = {} -- for resetting
|
|
|
|
---@param actor Actor
|
|
--- Resets an actor to its initial state
|
|
function reset(actor)
|
|
if not M._actorsInitialized then error('uranium: cannot reset an actor during initialization', 2) end
|
|
for _, q in ipairs(M._globalQueue) do
|
|
local queueActor = q[1]
|
|
if queueActor == actor.__raw then
|
|
local v = q[2]
|
|
|
|
local func = queueActor[v[1]]
|
|
if not func then
|
|
-- uhmmm ??? hm. what do we do??
|
|
else
|
|
oat._patchFunction(func, queueActor)(unpack(v[2]))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
resetActor = reset
|
|
|
|
M._actorQueue = {}
|
|
M._actorAssociationQueue = {}
|
|
|
|
M._actorTree = {}
|
|
M._currentPath = nil
|
|
M._pastPaths = {}
|
|
M._currentActor = nil
|
|
|
|
local function findFirstActor(path)
|
|
for i, v in ipairs(path) do
|
|
if v.type or v.file then
|
|
return v, i
|
|
end
|
|
end
|
|
end
|
|
|
|
local function findFirstActorFrame(path)
|
|
for i, v in ipairs(path) do
|
|
if not v.type and not v.file then
|
|
return v, i
|
|
end
|
|
end
|
|
end
|
|
|
|
oat._actor = {}
|
|
|
|
local function nextActor()
|
|
local new, idx = findFirstActor(M._currentPath)
|
|
if not new then
|
|
M._currentActor = nil
|
|
else
|
|
M._currentActor = new
|
|
table.remove(M._currentPath, idx)
|
|
end
|
|
end
|
|
|
|
function oat._actor.recurse(forceActor)
|
|
local newFrame, idx = findFirstActorFrame(M._currentPath)
|
|
local newActor = findFirstActor(M._currentPath)
|
|
if newFrame and not (newActor and forceActor) then
|
|
table.insert(M._pastPaths, M._currentPath)
|
|
M._currentPath = M._currentPath[idx]
|
|
table.remove(M._pastPaths[#M._pastPaths], idx)
|
|
return true
|
|
elseif newActor then
|
|
table.insert(M._pastPaths, M._currentPath)
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
function oat._actor.recurseLast()
|
|
return oat._actor.recurse(true)
|
|
end
|
|
|
|
function oat._actor.endRecurse()
|
|
M._currentPath = table.remove(M._pastPaths, #M._pastPaths)
|
|
end
|
|
|
|
function oat._actor.cond()
|
|
return M._currentActor ~= nil
|
|
end
|
|
|
|
function oat._actor.hasShader()
|
|
return oat._actor.cond() and (M._currentActor.frag ~= nil or M._currentActor.vert ~= nil)
|
|
end
|
|
|
|
function oat._actor.noShader()
|
|
nextActor()
|
|
return oat._actor.cond() and not oat._actor.hasShader()
|
|
end
|
|
|
|
function oat._actor.type()
|
|
return M._currentActor.type
|
|
end
|
|
|
|
function oat._actor.file()
|
|
return M._currentActor.file
|
|
end
|
|
|
|
function oat._actor.frag()
|
|
return M._currentActor.frag or 'nop.frag'
|
|
end
|
|
|
|
function oat._actor.vert()
|
|
return M._currentActor.vert or 'nop.vert'
|
|
end
|
|
|
|
function oat._actor.font()
|
|
return M._currentActor.font
|
|
end
|
|
|
|
function oat._actor.init(self)
|
|
M._currentActor.init(self)
|
|
self:removecommand('Init')
|
|
M._currentActor = nil -- to prevent any weirdness
|
|
end
|
|
|
|
function oat._actor.initFrame(self)
|
|
self:removecommand('Init')
|
|
self:SetDrawFunction(function()
|
|
for i = 1, self:GetNumChildren() do
|
|
local a = self:GetChildAt(i - 1)
|
|
if specialActorFrames[a] == false then
|
|
a:Draw()
|
|
end
|
|
end
|
|
end)
|
|
|
|
if M._currentPath.init then
|
|
M._currentPath.init(self)
|
|
M._currentPath.init = nil
|
|
specialActorFrames[self] = true
|
|
else
|
|
specialActorFrames[self] = false
|
|
end
|
|
end
|
|
|
|
local actorMethodOverrides = {
|
|
Draw = function(self, ...)
|
|
drawfunctionArguments[self] = arg
|
|
self.__raw:Draw()
|
|
end
|
|
}
|
|
|
|
-- todo: probably make this more sane
|
|
local function createProxyActor(name)
|
|
local queue = {}
|
|
local initCommands = {}
|
|
local lockedActor
|
|
local queueRepresentation
|
|
|
|
return setmetatable({}, {
|
|
__index = function(self, key)
|
|
if key == '__raw' then
|
|
return lockedActor
|
|
end
|
|
if lockedActor then
|
|
if actorMethodOverrides[key] then
|
|
return actorMethodOverrides[key]
|
|
else
|
|
local val = lockedActor[key]
|
|
if type(val) == 'function' then
|
|
return oat._patchFunction(val, lockedActor)
|
|
end
|
|
return val
|
|
end
|
|
end
|
|
if key == '__queue' then
|
|
return queueRepresentation
|
|
end
|
|
if key == '__queueRepresentation' then
|
|
return function(q)
|
|
queueRepresentation = q
|
|
end
|
|
end
|
|
if key == '__lock' then
|
|
return function(actor)
|
|
if lockedActor then return end
|
|
for _, v in ipairs(queue) do
|
|
local func = actor[v[1]]
|
|
if not func then
|
|
error(
|
|
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
|
|
'you\'re calling a function \'' .. v[1] .. '\' on a ' .. name .. ' which doesn\'t exist!:\n'
|
|
)
|
|
else
|
|
local success, result = pcall(function()
|
|
oat._patchFunction(func, actor)(unpack(v[2]))
|
|
end)
|
|
if not success then
|
|
error(
|
|
'uranium: error on \'' .. name .. '\' initialization on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
|
|
result
|
|
)
|
|
end
|
|
end
|
|
end
|
|
-- now that we know there's no poisonous methods in queue, let's offload them
|
|
for _, v in ipairs(queue) do
|
|
table.insert(M._globalQueue, {actor, v})
|
|
end
|
|
-- let's also properly route everything from the proxied actor to the actual actor
|
|
lockedActor = actor
|
|
-- and now let's run the initcommands
|
|
for _, c in ipairs(initCommands) do
|
|
local func = c[1]
|
|
local success, result = pcall(function()
|
|
func(actor)
|
|
end)
|
|
if not success then
|
|
error(
|
|
'uranium: error on \'' .. name .. '\' InitCommand defined on ' .. v[3].short_src .. ':' .. v[3].currentline .. ':\n' ..
|
|
result
|
|
)
|
|
end
|
|
end
|
|
-- to make mr. Garbage Collector's job easier
|
|
initCommands = {}
|
|
queueRepresentation = nil
|
|
queue = {}
|
|
end
|
|
else
|
|
return function(...)
|
|
if M._actorsInitialized then return end
|
|
if key == 'addcommand' and arg[2] == 'Init' then
|
|
table.insert(initCommands, {arg[3], debug.getinfo(2, 'Sl')})
|
|
else
|
|
table.insert(queue, {key, arg, debug.getinfo(2, 'Sl')})
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
__newindex = function()
|
|
error('uranium: cannot set properties on actors!', 2)
|
|
end,
|
|
__tostring = function() return 'Proxy of ' .. name end,
|
|
__name = name
|
|
})
|
|
end
|
|
|
|
local function createGenericFunc(type)
|
|
return function()
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
local actor = createProxyActor(type)
|
|
table.insert(M._actorQueue, {
|
|
type = type,
|
|
init = function(a)
|
|
actor.__lock(a)
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
end
|
|
|
|
--- Defines a Quad actor.
|
|
---@type fun(): Quad
|
|
Quad = createGenericFunc('Quad')
|
|
--- Defines an ActorProxy actor.
|
|
---@type fun(): ActorProxy
|
|
ActorProxy = createGenericFunc('ActorProxy')
|
|
--- Defines a Polygon actor.
|
|
---@type fun(): Polygon
|
|
Polygon = createGenericFunc('Polygon')
|
|
--- Defines an ActorFrameTexture actor.
|
|
---@type fun(): ActorFrameTexture
|
|
ActorFrameTexture = createGenericFunc('ActorFrameTexture')
|
|
|
|
---@param file string | nil
|
|
---@return Sprite
|
|
--- Defines a Sprite actor.
|
|
function Sprite(file)
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
--if not file then error('uranium: cannot create a Sprite without a file', 2) end
|
|
local actor = createProxyActor('Sprite')
|
|
local type = nil
|
|
if not file then type = 'Sprite' end
|
|
table.insert(M._actorQueue, {
|
|
type = type,
|
|
file = file and uranium.dir .. file,
|
|
init = function(a)
|
|
actor.__lock(a)
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
|
|
---@return ActorFrame
|
|
---@see addChild
|
|
--- Defines an ActorFrame. Add children to it with `addChild`.
|
|
function ActorFrame()
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
local actor = createProxyActor('ActorFrame')
|
|
table.insert(M._actorQueue, {
|
|
type = 'ActorFrame',
|
|
init = function(a)
|
|
actor.__lock(a)
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
oat._actorAssociationTable[actor] = {}
|
|
return actor
|
|
end
|
|
|
|
local function isShaderCode(str)
|
|
return string.find(str or '', '\n')
|
|
end
|
|
|
|
---@param frag string | nil
|
|
---@param vert string | nil
|
|
---@return RageShaderProgram
|
|
--- Defines a shader. `frag` and `vert` can either be filenames or shader code.
|
|
function Shader(frag, vert)
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
local actor = createProxyActor('RageShaderProgram')
|
|
|
|
local fragFile = frag
|
|
local vertFile = vert
|
|
|
|
local isFragShaderCode = isShaderCode(frag)
|
|
local isVertShaderCode = isShaderCode(vert)
|
|
|
|
if isFragShaderCode then fragFile = nil end
|
|
if isVertShaderCode then vertFile = nil end
|
|
|
|
if (frag and vert) and ((isFragShaderCode and not isVertShaderCode) or (not isFragShaderCode and isVertShaderCode)) then
|
|
error('uranium: cannot create a shader with 1 shader file and 1 shader code block', 2)
|
|
end
|
|
|
|
table.insert(M._actorQueue, {
|
|
type = 'Sprite',
|
|
frag = fragFile and ('../' .. fragFile) or 'nop.frag',
|
|
vert = vertFile and ('../' .. vertFile) or 'nop.vert',
|
|
init = function(a)
|
|
a:hidden(1)
|
|
actor.__lock(a:GetShader())
|
|
|
|
-- shader code stuff
|
|
if isFragShaderCode or isVertShaderCode then
|
|
a:GetShader():compile(vert or '', frag or '')
|
|
end
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
|
|
---@param file string
|
|
---@return RageTexture
|
|
--- Defines a texture.
|
|
function Texture(file)
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
if not file then error('uranium: cannot create a texture without a file', 2) end
|
|
local actor = createProxyActor('RageTexture')
|
|
|
|
table.insert(M._actorQueue, {
|
|
file = file and uranium.dir .. file,
|
|
init = function(a)
|
|
a:hidden(1)
|
|
actor.__lock(a:GetTexture())
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
|
|
---@param file string
|
|
---@return Model
|
|
--- Defines a Model actor.
|
|
function Model(file)
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
if not file then error('uranium: cannot create a Model without a file', 2) end
|
|
local actor = createProxyActor('Model')
|
|
table.insert(M._actorQueue, {
|
|
type = nil,
|
|
file = file and uranium.dir .. file,
|
|
init = function(a)
|
|
actor.__lock(a)
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
|
|
---@param font string?
|
|
---@param text string?
|
|
---@return BitmapText
|
|
--- Defines a BitmapText actor.
|
|
function BitmapText(font, text)
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
local actor = createProxyActor('BitmapText')
|
|
table.insert(M._actorQueue, {
|
|
type = 'BitmapText',
|
|
font = font or 'common',
|
|
init = function(a)
|
|
if text then a:settext(text) end
|
|
actor.__lock(a)
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
|
|
---@param file string
|
|
---@return ActorSound
|
|
--- Defines an ActorSound actor.
|
|
function ActorSound(file)
|
|
if M._actorsInitializing then error('uranium: cannot create an actor during actor initialization!!', 2) end
|
|
if M._actorsInitialized then error('uranium: cannot create an actor during runtime!!', 2) end
|
|
if not file then error('uranium: cannot create an ActorSound without a file', 2) end
|
|
local actor = createProxyActor('ActorSound')
|
|
table.insert(M._actorQueue, {
|
|
type = 'ActorSound',
|
|
file = uranium.dir .. file,
|
|
init = function(a)
|
|
actor.__lock(a)
|
|
end
|
|
})
|
|
actor.__queueRepresentation(M._actorQueue[#M._actorQueue])
|
|
return actor
|
|
end
|
|
|
|
---@param frame ActorFrame
|
|
---@param actor Actor
|
|
--- Adds a child to an ActorFrame. **Please be aware of the side-effects!**
|
|
function addChild(frame, actor)
|
|
if not frame or not actor then
|
|
error('uranium: frame and actor must both Exist', 2)
|
|
end
|
|
if M._actorsInitializing then
|
|
error('uranium: cannot create frame-child associations during actor initialization', 2)
|
|
end
|
|
if M._actorsInitialized then
|
|
error('uranium: cannot create frame-child associations after actors have been initialized', 2)
|
|
end
|
|
if not frame.__lock then
|
|
error('uranium: ActorFrame passed into addChild must be one instantiated with ActorFrame()!', 2)
|
|
end
|
|
if not actor.__lock then
|
|
error('uranium: trying to add a child to an ActorFrame that isn\'t an actor; please read the first half of \'ActorFrame\'', 2)
|
|
end
|
|
M._actorAssociationQueue[actor.__queue] = frame.__queue
|
|
table.insert(oat._actorAssociationTable[frame], actor)
|
|
end
|
|
|
|
function M._transformQueueToTree()
|
|
local tree = {}
|
|
local paths = {}
|
|
local iter = 0
|
|
while #M._actorQueue > 0 do
|
|
iter = iter + 1
|
|
if iter > 99999 then
|
|
error('uranium: failed to transform queue to tree: reached maximum iteration limit! is there an actor with an invalid actorframe?')
|
|
end
|
|
for i = #M._actorQueue, 1, -1 do
|
|
v = M._actorQueue[i]
|
|
local insertInto
|
|
if not M._actorAssociationQueue[v] then
|
|
insertInto = tree
|
|
else
|
|
if paths[M._actorAssociationQueue[v]] then
|
|
insertInto = paths[M._actorAssociationQueue[v]]
|
|
end
|
|
end
|
|
if insertInto then
|
|
if v.type == 'ActorFrame' then
|
|
table.insert(insertInto, {init = v.init})
|
|
table.remove(M._actorQueue, i)
|
|
paths[v] = insertInto[#insertInto]
|
|
else
|
|
table.insert(insertInto, v)
|
|
table.remove(M._actorQueue, i)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
M._actorTree = tree
|
|
end
|
|
|
|
function M.prepareForActors()
|
|
M._actorsInitializing = true
|
|
M._transformQueueToTree()
|
|
--Trace(fullDump(M._actorTree))
|
|
M._currentPath = M._actorTree
|
|
end
|
|
|
|
function M.finalize()
|
|
oat._actor = nil
|
|
|
|
M._actorQueue = nil
|
|
M._actorAssociationQueue = nil
|
|
|
|
M._actorTree = nil
|
|
M._currentPath = nil
|
|
M._pastPaths = nil
|
|
M._currentActor = nil
|
|
end
|
|
|
|
return M |