some gui hook cleanup, added keyboard/controller input for selecting gui options, fixed a crash when loading larger scenes because of low buffer size (or at least I believe it was that)

This commit is contained in:
ecker 2025-08-16 00:25:06 -05:00
parent b10ee9975a
commit cb1d9c4daf
24 changed files with 507 additions and 316 deletions

View File

@ -12,13 +12,15 @@
},
"metadata": {
"clickable": true,
"hoverable": true,
"events": {
"click": {
"name": "system:Quit",
"payload": {
"scene": "StartMenu"
}
},
"delay": 0.125
}
},

View File

@ -15,6 +15,8 @@ local camera = controller:getComponent("Camera")
local metadata = ent:getComponent("Metadata")
local masterdata = scene:getComponent("Metadata")
local soundEmitter = ent:loadChild("./sound.json",true)
local children = {
mainText = ent:loadChild("./main-text.json",true),
circleOut = ent:loadChild("./circle-out.json",true),
@ -28,6 +30,7 @@ if not timer:running() then timer:start() end
local playSound = function( key )
local url = "/ui/" .. key .. ".ogg"
soundEmitter:callHook("sound:Emit.%UID%", { filename = url })
-- local assetLoader = scene:getComponent("Asset")
-- assetLoader:cache(ent:formatHookName("asset:Load.%UID%"), string.resolveURI(url))
end
@ -66,105 +69,172 @@ ent:addHook("asset:Load.%UID%", function( json )
return true
end )
if os.arch() == "Dreamcast" then
ent:bind( "tick", function(self)
-- circle in
if children.circleIn:uid() > 0 then
local transform = children.circleIn:getComponent("Transform")
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), time.current() * -0.0125 )
end
-- circle out
if children.circleIn:uid() > 0 then
local transform = children.circleIn:getComponent("Transform")
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), time.current() * 0.0125 )
end
if (window.keyPressed("Enter") or inputs.key("START")) and timer:elapsed() >= 1 then
timer:reset()
children.start:callHook("gui:Clicked.%UID%", {})
end
end )
else
ent:bind( "tick", function(self)
--if (window.keyPressed("Enter") or inputs.key("START")) and timer:elapsed() >= 1 then
if inputs.key("START") and timer:elapsed() >= 1 then
timer:reset()
children.start:callHook("gui:Clicked.%UID%", {})
end
local selectableElements = {
children.start,
children.quit,
}
local selectableElementColors = {}
local selectedElement = 0
local selectionColor = { 1, 0, 1, 1 }
local INPUT_DELAY = 0.2
local static = Static.get(self)
if not static.alpha then
static.alpha = 0
local function array_index_of( haystack, needle )
for i, v in ipairs(haystack) do
if v == needle then
return i
end
end
return 0
end
local function onHover( payload )
playSound( "buttonrollover" )
selectedElement = 0
end
local function onClick( payload )
playSound( "buttonclickrelease" )
end
metadata["initialized"] = true;
if static.alpha >= 1.0 then
static.alpha = 1.0
for i, v in ipairs( selectableElements ) do
selectableElementColors[i] = nil
v:addHook("gui:HoverStart.%UID%", onHover )
v:addHook("gui:ClickStart.%UID%", onClick )
end
local function handleSelectionIndex()
if (inputs.key("L_DPAD_UP") or inputs.key("Up")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
-- nothing is selected
if selectedElement == 0 then
-- set to bottom
selectedElement = #selectableElements
else
static.alpha = static.alpha + time.delta() * 1.5
selectedElement = selectedElement - 1
-- wrap to bottom
if selectedElement <= 0 then
selectedElement = #selectableElements
end
end
playSound( "buttonrollover" )
end
if (inputs.key("L_DPAD_DOWN") or inputs.key("Down")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
-- nothing is selected
if selectedElement == 0 then
-- set to top
selectedElement = 1
else
selectedElement = selectedElement + 1
-- wrap to top
if selectedElement > #selectableElements then
selectedElement = 1
end
end
playSound( "buttonrollover" )
end
end
ent:bind( "tick", function(self)
handleSelectionIndex()
local static = Static.get(self)
if not static.alpha then
static.alpha = 0
end
metadata["initialized"] = true;
if static.alpha >= 1.0 then
static.alpha = 1.0
else
static.alpha = static.alpha + time.delta() * 1.5
end
-- make background glow
local glow = 1 + math.sin(1.25 * time.current()) * 0.125
metadata["color"] = { glow, glow, glow, static.alpha }
self:setComponent("Metadata", metadata)
camera:update(true);
-- iterate children in batch
for k, v in pairs(children) do
if v:uid() <= 0 then goto continue end
-- set alpha
local metadata = v:getComponent("Metadata")
local index = array_index_of( selectableElements, v )
-- mark element as hovered if selected
if 0 < selectedElement and selectedElement <= #selectableElements then
if 0 < index and index <= #selectableElements then
metadata["hovered"] = index == selectedElement
end
end
-- make background glow
local glow = 1 + math.sin(1.25 * time.current()) * 0.125
metadata["color"][1] = glow
metadata["color"][2] = glow
metadata["color"][3] = glow
metadata["alpha"] = static.alpha;
self:setComponent("Metadata", metadata)
camera:update(true);
-- iterate children in batch
for k, v in pairs(children) do
if v:uid() <= 0 then goto continue end
-- set alpha
local metadata = v:getComponent("Metadata")
metadata["alpha"] = static.alpha
v:setComponent("Metadata", metadata)
local transform = v:getComponent("Transform")
local static = Static.get(v)
-- translation
if not static.from then goto continue end
if not static.to then static.to = Vector3f(transform.position) end
if not static.delta then static.delta = 0 end
if static.delta >= 1 then
static.delta = 1
else
static.delta = static.delta + time.delta() * 1.5
transform.position = Vector3f.lerp( static.from, static.to, static.delta )
if metadata["clickable"] then
-- backup color
if selectableElementColors[index] == nil then
selectableElementColors[index] = metadata["color"]
end
::continue::
end
-- circle in
child = children.circleIn
if child:uid() > 0 then
local static = Static.get( child )
local transform = child:getComponent("Transform")
local metadata = child:getComponent("Metadata")
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * -speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
-- color for selection
if metadata["hovered"] then
metadata["color"] = selectionColor
-- simulate click on input press
if (inputs.key("A") or inputs.key("START") or inputs.key("Enter")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
v:callHook("gui:Clicked.%UID%", {})
end
else
metadata["color"] = selectableElementColors[index]
end
end
-- circle out
child = children.circleOut
if child:uid() > 0 then
local static = Static.get( child )
local transform = child:getComponent("Transform")
local metadata = child:getComponent("Metadata")
metadata["color"][4] = static.alpha
v:setComponent("Metadata", metadata)
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
local transform = v:getComponent("Transform")
local static = Static.get(v)
-- translation
if not static.from then goto continue end
if not static.to then static.to = Vector3f(transform.position) end
if not static.delta then static.delta = 0 end
if static.delta >= 1 then
static.delta = 1
else
static.delta = static.delta + time.delta() * 1.5
transform.position = Vector3f.lerp( static.from, static.to, static.delta )
end
end )
end
::continue::
end
-- circle in
child = children.circleIn
if child:uid() > 0 then
local static = Static.get( child )
local transform = child:getComponent("Transform")
local metadata = child:getComponent("Metadata")
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * -speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
-- circle out
child = children.circleOut
if child:uid() > 0 then
local static = Static.get( child )
local transform = child:getComponent("Transform")
local metadata = child:getComponent("Metadata")
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
end )

View File

@ -0,0 +1,38 @@
{
"type": "Object",
"name": "Sound Emitter",
"ignore": false,
"assets": [
],
"behaviors": [
"SoundEmitterBehavior"
],
"transform": {
"position": [ 0, 0, 0 ],
"rotation": {
"axis": [ 0, 1, 0 ],
"angle": 0
},
"scale": [ 1, 1, 1 ]
},
"system": {
"hot reload": {
"enabled": true
},
"defaults": {
"render": true,
"asset load": true
},
"load": {
"ignore": true
}
},
"metadata": {
"audio": {
"spatial": false,
"loop": false,
"volume": "sfx",
"rolloffFactor": 2
}
}
}

View File

@ -12,10 +12,12 @@
},
"metadata": {
"clickable": true,
"hoverable": true,
"events": {
"click": {
"name": "game:Scene.Load",
"payload": { "scene": "SourceEngine" }
"payload": { "scene": "SourceEngine" },
"delay": 0.125
}
},

View File

@ -12,10 +12,12 @@
},
"metadata": {
"clickable": true,
"hoverable": true,
"events": {
"click": {
"name": "menu:Close.%P-UID%",
"payload": {}
"payload": {},
"delay": 0.125
}
},

View File

@ -12,6 +12,7 @@
},
"metadata": {
"clickable": true,
"hoverable": true,
"events": {
"click": [
{
@ -25,7 +26,8 @@
"scope": "scene",
"delay": 0
}
}
},
"delay": 0.125
}
]
},

View File

@ -46,207 +46,218 @@ destination(children.quit, -1.5, nil, 0)
local playSound = function( key )
local url = "/ui/" .. key .. ".ogg"
soundEmitter:callHook("sound:Emit.%UID%", { filename = url })
-- local assetLoader = scene:getComponent("Asset")
-- assetLoader:cache(soundEmitter:formatHookName("asset:Load.%UID%"), string.resolveURI(url), "")
end
ent:addHook("menu:Close.%UID%", function( json )
playSound("menu close")
--playSound("menu close")
if metadata["system"]["hooks"] == nil then metadata["system"]["hooks"] = {} end
metadata["system"]["hooks"]["onClose"] = json["callback"];
metadata["system"]["closing"] = true;
ent:setComponent("Metadata", metadata)
end )
playSound("menu open")
--playSound("menu open")
if os.arch() == "Dreamcast" then
ent:bind( "tick", function(self)
local static = Static.get(self)
if not static.alpha then
local selectableElements = {
children.closeOption,
children.quit,
}
local selectableElementColors = {}
local selectedElement = 0
local selectionColor = { 1, 0, 1, 1 }
local INPUT_DELAY = 0.2
local function array_index_of( haystack, needle )
for i, v in ipairs(haystack) do
if v == needle then
return i
end
end
return 0
end
local function onHover( payload )
playSound( "buttonrollover" )
selectedElement = 0
end
local function onClick( payload )
playSound( "buttonclickrelease" )
end
for i, v in ipairs( selectableElements ) do
selectableElementColors[i] = nil
v:addHook("gui:HoverStart.%UID%", onHover )
v:addHook("gui:ClickStart.%UID%", onClick )
end
local function handleSelectionIndex()
if (inputs.key("L_DPAD_UP") or inputs.key("Up")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
-- nothing is selected
if selectedElement == 0 then
-- set to bottom
selectedElement = #selectableElements
else
selectedElement = selectedElement - 1
-- wrap to bottom
if selectedElement <= 0 then
selectedElement = #selectableElements
end
end
playSound( "buttonrollover" )
end
if (inputs.key("L_DPAD_DOWN") or inputs.key("Down")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
-- nothing is selected
if selectedElement == 0 then
-- set to top
selectedElement = 1
else
selectedElement = selectedElement + 1
-- wrap to top
if selectedElement > #selectableElements then
selectedElement = 1
end
end
playSound( "buttonrollover" )
end
end
ent:bind( "tick", function(self)
handleSelectionIndex()
local static = Static.get(self)
if not static.alpha then
static.alpha = 0
end
if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
self:callHook("menu:Close.%UID%", {})
end
-- handle closing
if metadata["system"]["closing"] then
if static.alpha <= 0 then
static.alpha = 0
metadata["system"]["closing"] = false
metadata["system"]["closed"] = true
else
static.alpha = static.alpha - time.delta()
end
elseif metadata["system"]["closed"] then
timer:stop()
local callback = metadata["system"]["hooks"]["onClose"]
if callback then
local payload = callback["payload"]
local target = self
if callback["scope"] == "parent" then
target = ent:getParent()
elseif callback["scope"] == "scene" then
target = scene
end
if type(callback["delay"]) == "number" and target:uid() ~= self:uid() then
target:queueHook( callback["name"], payload, callback["delay"] );
else
target:callHook( callback["name"], payload );
end
end
entities.destroy(self)
-- scene:queueHook("system:Destroy", { uid = self:uid() }, 0)
return
else
if not metadata["initialized"] then
static.alpha = 0
end
if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= 1 then
timer:reset()
self:callHook("menu:Close.%UID%", {})
end
-- handle closing
if metadata["system"]["closing"] then
if static.alpha <= 0 then
static.alpha = 0
metadata["system"]["closing"] = false
metadata["system"]["closed"] = true
else
static.alpha = static.alpha - time.delta()
end
elseif metadata["system"]["closed"] then
timer:stop()
local callback = metadata["system"]["hooks"]["onClose"]
if callback then
local payload = callback["payload"]
local target = self
if callback["scope"] == "parent" then
target = ent:getParent()
elseif callback["scope"] == "scene" then
target = scene
end
if type(callback["delay"]) == "number" and target:uid() ~= self:uid() then
target:queueHook( callback["name"], payload, callback["delay"] );
else
target:callHook( callback["name"], payload );
end
end
controller:callHook("system:Control.%UID%", {
control = true
})
entities.destroy(self)
-- scene:queueHook("system:Destroy", { uid = self:uid() }, 0)
return
metadata["initialized"] = true;
if static.alpha >= 1.0 then
static.alpha = 1.0
else
if not metadata["initialized"] then
static.alpha = 0
end
metadata["initialized"] = true;
if static.alpha >= 1.0 then
static.alpha = 1.0
else
static.alpha = static.alpha + time.delta() * 1.5
static.alpha = static.alpha + time.delta() * 1.5
end
end
metadata["color"][4] = static.alpha;
-- set alphas
for k, v in pairs(children) do
if v:uid() <= 0 then goto continue end
-- set alpha
local metadata = v:getComponent("Metadata")
local index = array_index_of( selectableElements, v )
-- mark element as hovered if selected
if 0 < selectedElement and selectedElement <= #selectableElements then
if 0 < index and index <= #selectableElements then
metadata["hovered"] = index == selectedElement
end
end
-- circle in
if children.circleIn:uid() > 0 then
local static = Static.get( children.circleIn )
local transform = children.circleIn:getComponent("Transform")
-- rotation
local speed = 0.0125
static.time = (static.time or 0) + time.delta() * -speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
-- circle out
if children.circleOut:uid() > 0 then
local static = Static.get( children.circleOut )
local transform = children.circleOut:getComponent("Transform")
-- rotation
local speed = 0.0125
static.time = (static.time or 0) + time.delta() * speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
end )
else
ent:bind( "tick", function(self)
local static = Static.get(self)
if not static.alpha then
static.alpha = 0
end
if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= 1 then
timer:reset()
self:callHook("menu:Close.%UID%", {})
end
-- handle closing
if metadata["system"]["closing"] then
if static.alpha <= 0 then
static.alpha = 0
metadata["system"]["closing"] = false
metadata["system"]["closed"] = true
else
static.alpha = static.alpha - time.delta()
if metadata["clickable"] then
-- backup color
if selectableElementColors[index] == nil then
selectableElementColors[index] = metadata["color"]
end
elseif metadata["system"]["closed"] then
timer:stop()
local callback = metadata["system"]["hooks"]["onClose"]
if callback then
local payload = callback["payload"]
local target = self
if callback["scope"] == "parent" then
target = ent:getParent()
elseif callback["scope"] == "scene" then
target = scene
end
if type(callback["delay"]) == "number" and target:uid() ~= self:uid() then
target:queueHook( callback["name"], payload, callback["delay"] );
else
target:callHook( callback["name"], payload );
-- color for selection
if metadata["hovered"] then
metadata["color"] = selectionColor
-- simulate click on input press
if (inputs.key("A") or inputs.key("Enter")) and timer:elapsed() >= INPUT_DELAY then
timer:reset()
v:callHook("gui:Clicked.%UID%", {})
end
else
metadata["color"] = selectableElementColors[index]
end
end
metadata["color"][4] = static.alpha
v:setComponent("Metadata", metadata)
entities.destroy(self)
-- scene:queueHook("system:Destroy", { uid = self:uid() }, 0)
return
local transform = v:getComponent("Transform")
local static = Static.get(v)
-- translation
if not static.from then goto continue end
if not static.to then static.to = Vector3f(transform.position) end
if not static.delta then static.delta = 0 end
if static.delta >= 1 then
static.delta = 1
else
if not metadata["initialized"] then
static.alpha = 0
end
metadata["initialized"] = true;
if static.alpha >= 1.0 then
static.alpha = 1.0
else
static.alpha = static.alpha + time.delta() * 1.5
end
static.delta = static.delta + time.delta() * 1.5
transform.position = Vector3f.lerp( static.from, static.to, static.delta )
end
::continue::
end
metadata["alpha"] = static.alpha;
-- set alphas
for k, v in pairs(children) do
if v:uid() <= 0 then goto continue end
-- circle in
if children.circleIn:uid() > 0 then
local static = Static.get( children.circleIn )
-- set alpha
local metadata = v:getComponent("Metadata")
metadata["alpha"] = static.alpha
local transform = children.circleIn:getComponent("Transform")
local metadata = children.circleIn:getComponent("Metadata")
local transform = v:getComponent("Transform")
local static = Static.get(v)
-- translation
if not static.from then goto continue end
if not static.to then static.to = Vector3f(transform.position) end
if not static.delta then static.delta = 0 end
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * -speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
-- circle out
if children.circleOut:uid() > 0 then
local static = Static.get( children.circleOut )
if static.delta >= 1 then
static.delta = 1
else
static.delta = static.delta + time.delta() * 1.5
transform.position = Vector3f.lerp( static.from, static.to, static.delta )
v:setComponent("Metadata", metadata)
end
local transform = children.circleOut:getComponent("Transform")
local metadata = children.circleOut:getComponent("Metadata")
::continue::
end
-- circle in
if children.circleIn:uid() > 0 then
local static = Static.get( children.circleIn )
local transform = children.circleIn:getComponent("Transform")
local metadata = children.circleIn:getComponent("Metadata")
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * -speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
-- circle out
if children.circleOut:uid() > 0 then
local static = Static.get( children.circleOut )
local transform = children.circleOut:getComponent("Transform")
local metadata = children.circleOut:getComponent("Metadata")
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
end )
end
-- rotation
local speed = metadata["hovered"] and 0.25 or 0.0125
static.time = (static.time or 0) + time.delta() * speed
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end
end )

View File

@ -76,7 +76,7 @@
"shadows": true
},
"stream": {
"tag": "worldspawn",
"tag": "", // worldspawn",
"player": "info_player_spawn",
"enabled": true, // "auto",
"radius": 16,

View File

@ -20,7 +20,7 @@
{ "filename": "./playerModel.json", "delay": 1 },
// "./playerModel.json",
"./playerLight.json",
"./playerHands.json",
// "./playerHands.json",
"./scripts/player.lua"
],
"system": {

View File

@ -2,12 +2,12 @@
"import": "./base_sourceengine.json",
"assets": [
// { "filename": "./models/animal_crossing.glb" },
{ "filename": "./models/animal_crossing/graph.json" },
{ "filename": "./models/animal_crossing/graph.json" }/*,
// { "filename": "./models/animal_crossing_small.glb" },
// { "filename": "./models/animal_crossing_small/graph.json" },
{ "filename": "/craeture.json", "delay": 2.0 },
{ "filename": "/craeture2.json", "delay": 2.0 }
{ "filename": "/craeture2.json", "delay": 2.0 }*/
],
"metadata": {
"graph": {

View File

@ -11,12 +11,12 @@
// exact matches
"worldspawn": {
"physics": { "type": "mesh", "static": true },
"grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
"grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
"optimize meshlets": { "simplify": 0.125, "print": false },
"unwrap mesh": true
},
"worldspawn_skybox": {
"grid": { "size": [16,1,16], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
"grid": { "size": [8,1,8], "epsilon": 0.001, "cleanup": true, "print": true, "clip": true },
"optimize meshlets": { "simplify": 0.125, "print": false },
"unwrap mesh": true
},

View File

@ -4,16 +4,16 @@
// { "filename": "./models/ss2_medsci1.glb" }
// { "filename": "./models/ss2_medsci1/graph.json" }
// { "filename": "./models/ss2_medsci1_small.glb" }
{ "filename": "./models/ss2_medsci1_small/graph.json" }
// { "filename": "./models/ss2_medsci1_small/graph.json" }
// { "filename": "./models/ss2_medsci1_smallish.glb" }
// { "filename": "./models/ss2_medsci1_smallish/graph.json" }
{ "filename": "./models/ss2_medsci1_smallish/graph.json" }
],
"metadata": {
"graph": {
"bgm": "./audio/music/medsci1.ogg",
"tags": {
"/^prop_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } },
"/^func_/": { "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } }
"/^prop_/": { "ignore": true, "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } },
"/^func_/": { "ignore": true, "action": "load", "payload": { "import": "/prop.json", "metadata": { "physics": { "gravity": [ 0, 0, 0 ] } } } }
}
}
}

View File

@ -17,7 +17,7 @@
}
},
"graph": {
"initial buffer elements": 128
"initial buffer elements": 256
},
"ext": {
"opengl": {

View File

@ -1 +1 @@
opengl
vulkan

View File

@ -5,4 +5,15 @@ This folder stores any code for external dependencies because my environment has
Separate per-target copies of dependencies may exist here as well, such as:
* Dreamcast
All code under this folder is not mine and are licensed under their respective licenses.
All code under this folder is not mine and are licensed under their respective licenses.
## Forked Dependencies
Some dependencies of mine rely on some modifications of the original dependencies for extra features or to function on other platforms.
* [nlohmann/json](https://github.com/nlohmann/json): Dreamcast only; requires some hacks for `m4-single-only` due to `sizeof(float) == sizeof(double)` (for prior toolchains, this allegedly changed but I still get a crash with an unmodified `nlohmann/json`)
* this is a header-only project so its changes live locally under `./dep/dreamcast/include/nlohmann/`
* [DanielChappuis/reactphysics3d](https://git.ecker.tech/ecker/reactphysics3d): adds in support for `float16`/`bfloat16`/quantized `uint16_t` triangle meshes
* Dreamcast requires some edits due to `sizeof(int) != sizeof(int32)` / `sizeof(uint) != sizeof(uint32)`, extra compile flags, and disabling exceptions
* [simulant/GLdc](https://git.ecker.tech/ecker/GLdc/): Dreamcast only; adds in support for `float16`/`bfloat16`/quantized `uint16_t` vertex data
* [GPUOpen-Effects/FidelityFX-FSR2](https://git.ecker.tech/ecker/FidelityFX-FSR2): modifications required for compiling under GCC (despite being a soft-dependency that I'm not making use of anyways)

View File

@ -158,8 +158,8 @@ namespace uf {
void UF_API render();
void UF_API destroy( bool soft = false );
void UF_API initialize( uf::Object& );
void UF_API initialize( pod::Graph::Storage& );
void UF_API initialize( uf::Object&, size_t = uf::graph::initialBufferElements );
void UF_API initialize( pod::Graph::Storage&, size_t = uf::graph::initialBufferElements );
void UF_API tick( uf::Object& );
void UF_API tick( pod::Graph::Storage& );
void UF_API render( uf::Object& );

View File

@ -327,22 +327,41 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
int mouseX = payload.mouse.position.x;
int mouseY = payload.mouse.position.y;
}
if ( !metadata.ui.click.ed && clicked ) {
this->callHook("gui:ClickStart.%UID%", payload);
} else if ( metadata.ui.click.ed && !clicked ) {
this->callHook("gui:ClickEnd.%UID%", payload);
}
metadata.ui.click.ed = clicked;
if ( clicked ) {
this->callHook("gui:Clicked.%UID%", payload);
}
this->callHook("gui:Mouse.Clicked.%UID%", payload);
} );
this->addHook( "gui:Clicked.%UID%", [&]( ext::json::Value& json ){
this->addHook( "gui:ClickStart.%UID%", [&]( pod::payloads::windowMouseClick& payload ){
uf::Serializer jsonPayload;
this->callHook("gui:ClickStart.%UID%", jsonPayload);
});
this->addHook( "gui:ClickEnd.%UID%", [&]( pod::payloads::windowMouseClick& payload ){
uf::Serializer jsonPayload;
this->callHook("gui:ClickEnd.%UID%", jsonPayload);
});
this->addHook( "gui:Clicked.%UID%", [&](ext::json::Value& json){
if ( ext::json::isNull( json ) ) return;
pod::payloads::windowMouseClick payload;
this->callHook("gui:Clicked.%UID%", payload);
});
this->addHook( "gui:Clicked.%UID%", [&](pod::payloads::windowMouseClick& payload){
uf::Serializer jsonPayload;
this->callHook("gui:Clicked.%UID%", jsonPayload);
if ( ext::json::isObject( metadataJson["events"]["click"] ) ) {
ext::json::Value event = metadataJson["events"]["click"];
metadataJson["events"]["click"] = ext::json::array();
@ -398,36 +417,58 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
int mouseX = payload.mouse.position.x;
int mouseY = payload.mouse.position.y;
}
metadata.ui.hover.ed = hovered;
if ( hovered && hoverTimer.elapsed().asDouble() >= 1 ) {
hoverTimer.reset();
uf::Serializer jsonPayload = ext::json::null();
// to-do: do something about trying to trigger json-bound hooks
if ( !metadata.ui.hover.ed && hovered ) {
this->callHook("gui:HoverStart.%UID%", payload);
} else if ( metadata.ui.hover.ed && !hovered ) {
this->callHook("gui:HoverEnd.%UID%", payload);
} else if ( hovered ) { /*&& hoverTimer.elapsed().asDouble() >= 1 ) {
hoverTimer.reset();*/
this->callHook("gui:Hovered.%UID%", payload);
}
metadata.ui.hover.ed = hovered;
this->callHook("gui:Mouse.Moved.%UID%", payload);
});
this->addHook( "gui:HoverStart.%UID%", [&]( pod::payloads::windowMouseMoved& payload ){
uf::Serializer jsonPayload;
this->callHook("gui:HoverStart.%UID%", jsonPayload);
});
this->addHook( "gui:HoverEnd.%UID%", [&]( pod::payloads::windowMouseMoved& payload ){
uf::Serializer jsonPayload;
this->callHook("gui:HoverEnd.%UID%", jsonPayload);
});
this->addHook( "gui:Hovered.%UID%", [&](ext::json::Value& json){
if ( ext::json::isNull( json ) ) return;
pod::payloads::windowMouseMoved payload;
this->callHook("gui:Hovered.%UID%", payload);
});
this->addHook( "gui:Hovered.%UID%", [&](pod::payloads::windowMouseMoved& payload){
uf::Serializer jsonPayload;
this->callHook("gui:Hovered.%UID%", jsonPayload);
if ( ext::json::isObject( metadataJson["events"]["hover"] ) ) {
ext::json::Value event = metadataJson["events"]["hover"];
metadataJson["events"]["hover"] = ext::json::array(); //Json::arrayValue;
metadataJson["events"]["hover"] = ext::json::array();
metadataJson["events"]["hover"][0] = event;
} else if ( !ext::json::isArray( metadataJson["events"]["hover"] ) ) {
this->getParent().as<uf::Object>().callHook("gui:Clicked.%UID%", json);
this->getParent().as<uf::Object>().callHook("gui:Hovered.%UID%", payload);
return;
}
for ( int i = 0; i < metadataJson["events"]["hover"].size(); ++i ) {
ext::json::Value event = metadataJson["events"]["hover"][i];
ext::json::Value payload = event["payload"];
float delay = event["delay"].as<float>();
if ( event["delay"].is<double>() ) {
if ( event["delay"].is<float>() ) {
this->queueHook(event["name"].as<uf::stl::string>(), payload, event["delay"].as<float>());
} else {
this->callHook(event["name"].as<uf::stl::string>(), payload );
}
}
return;
});
}
}

View File

@ -513,7 +513,7 @@ void ext::PlayerBehavior::tick( uf::Object& self ) {
uf::transform::rotate( cameraTransform, cameraTransform.right, lookDelta.y );
} else metadata.camera.limit.current.y -= lookDelta.y;
}
} else {
} else if ( metadata.system.control ) {
if ( keys.lookRight ^ keys.lookLeft ) {
if ( collider.body ) uf::physics::impl::applyRotation( collider, transform.up, speed.rotate * (keys.lookRight ? 1 : -1) ); else
uf::transform::rotate( transform, transform.up, speed.rotate * (keys.lookRight ? 1 : -1) );

View File

@ -132,11 +132,8 @@ namespace {
}
}
#if UF_ENV_DREAMCAST
size_t uf::graph::initialBufferElements = 256;
#else
size_t uf::graph::initialBufferElements = 1024;
#endif
size_t uf::graph::initialBufferElements = 1024;
bool uf::graph::globalStorage;
pod::Graph::Storage uf::graph::storage;
@ -1834,18 +1831,19 @@ void uf::graph::initialize() {
if ( uf::graph::globalStorage ) return uf::graph::initialize( uf::graph::storage );
return uf::graph::initialize( uf::scene::getCurrentScene() );
}
void uf::graph::initialize( uf::Object& object ) {
return uf::graph::initialize( object.getComponent<pod::Graph::Storage>() );
void uf::graph::initialize( uf::Object& object, size_t initialElements ) {
return uf::graph::initialize( object.getComponent<pod::Graph::Storage>(), initialElements );
}
void uf::graph::initialize( pod::Graph::Storage& storage ) {
void uf::graph::initialize( pod::Graph::Storage& storage, size_t initialElements ) {
storage.buffers.camera.initialize( (const void*) nullptr, sizeof(pod::Camera::Viewports), uf::renderer::enums::Buffer::UNIFORM );
storage.buffers.drawCommands.initialize( (const void*) nullptr, sizeof(pod::DrawCommand) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.instance.initialize( (const void*) nullptr, sizeof(pod::Instance) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.instanceAddresses.initialize( (const void*) nullptr, sizeof(pod::Instance::Addresses) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.joint.initialize( (const void*) nullptr, sizeof(pod::Matrix4f) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.material.initialize( (const void*) nullptr, sizeof(pod::Material) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.texture.initialize( (const void*) nullptr, sizeof(pod::Texture) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.light.initialize( (const void*) nullptr, sizeof(pod::Light) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE );
// to-do: check if opengl really needs these
storage.buffers.drawCommands.initialize( (const void*) nullptr, sizeof(pod::DrawCommand) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.instance.initialize( (const void*) nullptr, sizeof(pod::Instance) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.instanceAddresses.initialize( (const void*) nullptr, sizeof(pod::Instance::Addresses) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.joint.initialize( (const void*) nullptr, sizeof(pod::Matrix4f) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.material.initialize( (const void*) nullptr, sizeof(pod::Material) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.texture.initialize( (const void*) nullptr, sizeof(pod::Texture) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.light.initialize( (const void*) nullptr, sizeof(pod::Light) * initialElements, uf::renderer::enums::Buffer::STORAGE );
}
void uf::graph::tick() {
if ( uf::graph::globalStorage ) return uf::graph::tick( uf::graph::storage );
@ -2037,6 +2035,7 @@ void uf::graph::destroy( pod::Graph::Storage& storage, bool soft ) {
}
void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
if ( !(0 <= node.mesh && node.mesh < graph.meshes.size()) ) return;
if ( !node.entity ) return;
auto& scene = uf::scene::getCurrentScene();
auto& storage = uf::graph::globalStorage ? uf::graph::storage : scene.getComponent<pod::Graph::Storage>();
@ -2074,7 +2073,7 @@ void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
// disable if not tagged for streaming
// to-do: check tag
if ( node.name != graph.settings.stream.tag ) {
if ( graph.settings.stream.tag != "" && node.name != graph.settings.stream.tag ) {
radius = 0;
}

View File

@ -42,7 +42,7 @@ ext::json::Value ext::json::find( const uf::stl::string& needle, const ext::json
if ( needle == key ) {
exact = value;
breaks = true;
} else if ( uf::string::isRegex( key ) && uf::string::matched( needle, key ) ) {
} else if ( uf::string::isRegex( key ) && uf::string::matched( needle, key ) && ext::json::isNull( regexed ) ) {
regexed = value;
}
});

View File

@ -196,13 +196,24 @@ namespace binds {
uf::Object& getParent( uf::Object& self ){
return self.getParent().as<uf::Object>();
}
void addHook( uf::Object& self, const uf::stl::string& name, sol::function function ) {
self.addHook( name, [function](ext::json::Value& json){
void addHook( uf::Object& self, const uf::stl::string& name, sol::protected_function fun ) {
self.addHook( name, [fun](ext::json::Value& json){
// cringe
if ( ext::json::isNull( json ) ) {
auto result = fun();
if ( !result.valid() ) {
sol::error err = result;
uf::iostream << err.what() << "\n";
return;
}
return;
}
uf::stl::string payload = json.dump();
auto decoded = ext::lua::decode( payload );
if ( !decoded ) return;
sol::table table = decoded.value();
auto result = function( table );
auto result = fun( table );
if ( !result.valid() ) {
sol::error err = result;
uf::iostream << err.what() << "\n";

View File

@ -217,7 +217,7 @@ void ext::opengl::initialize() {
renderMode->initialize(device);
}
uf::graph::initialize();
// uf::graph::initialize();
auto tasks = uf::thread::schedule(settings::invariant::multithreadedRecording);
for ( auto& renderMode : renderModes ) { if ( !renderMode ) continue;

View File

@ -323,6 +323,8 @@ uf::stl::string spec::dreamcast::pvr_malloc_stats( bool verbose ) {
spec::dreamcast::Window::Window() : m_context(NULL) {}
void spec::dreamcast::Window::create( const spec::dreamcast::Window::vector_t& _size, const spec::dreamcast::Window::title_t& title ) {
// gdb_init(); // i guess this is specifically when using dcload and not an emulator with gdb support......
::keyboard.device = maple_enum_type(1, MAPLE_FUNC_KEYBOARD);
this->setSize(_size);

View File

@ -4,7 +4,7 @@ CC = gcc
CXX = $(KOS_CCPLUS)
RENDERER = opengl
TARGET_EXTENSION = elf
OPTIMIZATIONS = -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -fstrict-aliasing -ffast-math -fno-unroll-all-loops -fno-optimize-sibling-calls -fschedule-insns2 -fomit-frame-pointer -DUF_NO_EXCEPTIONS -fno-exceptions -flto -g
OPTIMIZATIONS = -Os -ffunction-sections -fdata-sections -Wl,--gc-sections -fstrict-aliasing -ffast-math -fno-unroll-all-loops -fno-optimize-sibling-calls -fschedule-insns2 -fomit-frame-pointer -DUF_NO_EXCEPTIONS -fno-exceptions -g # -flto
WARNINGS = -Wno-attributes -Wno-conversion-null
FLAGS += $(KOS_CPPFLAGS) -m4-single -std=c++17 $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always
INCS += $(KOS_INC_PATHS) -I/opt/dreamcast/sh-elf/sh-elf/include