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": { "metadata": {
"clickable": true, "clickable": true,
"hoverable": true,
"events": { "events": {
"click": { "click": {
"name": "system:Quit", "name": "system:Quit",
"payload": { "payload": {
"scene": "StartMenu" "scene": "StartMenu"
} },
"delay": 0.125
} }
}, },

View File

@ -15,6 +15,8 @@ local camera = controller:getComponent("Camera")
local metadata = ent:getComponent("Metadata") local metadata = ent:getComponent("Metadata")
local masterdata = scene:getComponent("Metadata") local masterdata = scene:getComponent("Metadata")
local soundEmitter = ent:loadChild("./sound.json",true)
local children = { local children = {
mainText = ent:loadChild("./main-text.json",true), mainText = ent:loadChild("./main-text.json",true),
circleOut = ent:loadChild("./circle-out.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 playSound = function( key )
local url = "/ui/" .. key .. ".ogg" local url = "/ui/" .. key .. ".ogg"
soundEmitter:callHook("sound:Emit.%UID%", { filename = url })
-- local assetLoader = scene:getComponent("Asset") -- local assetLoader = scene:getComponent("Asset")
-- assetLoader:cache(ent:formatHookName("asset:Load.%UID%"), string.resolveURI(url)) -- assetLoader:cache(ent:formatHookName("asset:Load.%UID%"), string.resolveURI(url))
end end
@ -66,31 +69,73 @@ ent:addHook("asset:Load.%UID%", function( json )
return true return true
end ) end )
if os.arch() == "Dreamcast" then
ent:bind( "tick", function(self) local selectableElements = {
-- circle in children.start,
if children.circleIn:uid() > 0 then children.quit,
local transform = children.circleIn:getComponent("Transform") }
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), time.current() * -0.0125 ) 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
-- circle out end
if children.circleIn:uid() > 0 then return 0
local transform = children.circleIn:getComponent("Transform") end
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), time.current() * 0.0125 ) local function onHover( payload )
playSound( "buttonrollover" )
selectedElement = 0
end
local function onClick( payload )
playSound( "buttonclickrelease" )
end end
if (window.keyPressed("Enter") or inputs.key("START")) and timer:elapsed() >= 1 then for i, v in ipairs( selectableElements ) do
timer:reset() selectableElementColors[i] = nil
children.start:callHook("gui:Clicked.%UID%", {}) v:addHook("gui:HoverStart.%UID%", onHover )
v:addHook("gui:ClickStart.%UID%", onClick )
end end
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 else
ent:bind( "tick", function(self) selectedElement = selectedElement - 1
--if (window.keyPressed("Enter") or inputs.key("START")) and timer:elapsed() >= 1 then -- wrap to bottom
if inputs.key("START") and timer:elapsed() >= 1 then if selectedElement <= 0 then
timer:reset() selectedElement = #selectableElements
children.start:callHook("gui:Clicked.%UID%", {})
end 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) local static = Static.get(self)
if not static.alpha then if not static.alpha then
@ -98,6 +143,7 @@ else
end end
metadata["initialized"] = true; metadata["initialized"] = true;
if static.alpha >= 1.0 then if static.alpha >= 1.0 then
static.alpha = 1.0 static.alpha = 1.0
else else
@ -106,10 +152,7 @@ else
-- make background glow -- make background glow
local glow = 1 + math.sin(1.25 * time.current()) * 0.125 local glow = 1 + math.sin(1.25 * time.current()) * 0.125
metadata["color"][1] = glow metadata["color"] = { glow, glow, glow, static.alpha }
metadata["color"][2] = glow
metadata["color"][3] = glow
metadata["alpha"] = static.alpha;
self:setComponent("Metadata", metadata) self:setComponent("Metadata", metadata)
camera:update(true); camera:update(true);
@ -120,7 +163,34 @@ else
-- set alpha -- set alpha
local metadata = v:getComponent("Metadata") local metadata = v:getComponent("Metadata")
metadata["alpha"] = static.alpha 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
if metadata["clickable"] then
-- backup color
if selectableElementColors[index] == nil then
selectableElementColors[index] = metadata["color"]
end
-- 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
metadata["color"][4] = static.alpha
v:setComponent("Metadata", metadata) v:setComponent("Metadata", metadata)
local transform = v:getComponent("Transform") local transform = v:getComponent("Transform")
@ -137,6 +207,7 @@ else
transform.position = Vector3f.lerp( static.from, static.to, static.delta ) transform.position = Vector3f.lerp( static.from, static.to, static.delta )
end end
::continue:: ::continue::
end end
@ -167,4 +238,3 @@ else
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time ) transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end end
end ) 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": { "metadata": {
"clickable": true, "clickable": true,
"hoverable": true,
"events": { "events": {
"click": { "click": {
"name": "game:Scene.Load", "name": "game:Scene.Load",
"payload": { "scene": "SourceEngine" } "payload": { "scene": "SourceEngine" },
"delay": 0.125
} }
}, },

View File

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

View File

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

View File

@ -46,109 +46,95 @@ destination(children.quit, -1.5, nil, 0)
local playSound = function( key ) local playSound = function( key )
local url = "/ui/" .. key .. ".ogg" local url = "/ui/" .. key .. ".ogg"
soundEmitter:callHook("sound:Emit.%UID%", { filename = url })
-- local assetLoader = scene:getComponent("Asset") -- local assetLoader = scene:getComponent("Asset")
-- assetLoader:cache(soundEmitter:formatHookName("asset:Load.%UID%"), string.resolveURI(url), "") -- assetLoader:cache(soundEmitter:formatHookName("asset:Load.%UID%"), string.resolveURI(url), "")
end end
ent:addHook("menu:Close.%UID%", function( json ) ent:addHook("menu:Close.%UID%", function( json )
playSound("menu close") --playSound("menu close")
if metadata["system"]["hooks"] == nil then metadata["system"]["hooks"] = {} end if metadata["system"]["hooks"] == nil then metadata["system"]["hooks"] = {} end
metadata["system"]["hooks"]["onClose"] = json["callback"]; metadata["system"]["hooks"]["onClose"] = json["callback"];
metadata["system"]["closing"] = true; metadata["system"]["closing"] = true;
ent:setComponent("Metadata", metadata) ent:setComponent("Metadata", metadata)
end ) end )
playSound("menu open") --playSound("menu open")
if os.arch() == "Dreamcast" then local selectableElements = {
ent:bind( "tick", function(self) children.closeOption,
local static = Static.get(self) children.quit,
if not static.alpha then }
static.alpha = 0 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 end
if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= 1 then 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() timer:reset()
self:callHook("menu:Close.%UID%", {}) -- nothing is selected
end if selectedElement == 0 then
-- set to bottom
-- handle closing selectedElement = #selectableElements
if metadata["system"]["closing"] then
if static.alpha <= 0 then
static.alpha = 0
metadata["system"]["closing"] = false
metadata["system"]["closed"] = true
else else
static.alpha = static.alpha - time.delta() selectedElement = selectedElement - 1
-- wrap to bottom
if selectedElement <= 0 then
selectedElement = #selectableElements
end 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 end
playSound( "buttonrollover" )
if type(callback["delay"]) == "number" and target:uid() ~= self:uid() then end
target:queueHook( callback["name"], payload, callback["delay"] ); 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 else
target:callHook( callback["name"], payload ); selectedElement = selectedElement + 1
-- wrap to top
if selectedElement > #selectableElements then
selectedElement = 1
end
end
playSound( "buttonrollover" )
end end
end end
controller:callHook("system:Control.%UID%", {
control = true
})
entities.destroy(self)
-- scene:queueHook("system:Destroy", { uid = self:uid() }, 0)
return
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
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) ent:bind( "tick", function(self)
handleSelectionIndex()
local static = Static.get(self) local static = Static.get(self)
if not static.alpha then if not static.alpha then
static.alpha = 0 static.alpha = 0
end end
if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= 1 then if (window.keyPressed("Escape") or inputs.key("START")) and timer:elapsed() >= INPUT_DELAY then
timer:reset() timer:reset()
self:callHook("menu:Close.%UID%", {}) self:callHook("menu:Close.%UID%", {})
end end
@ -196,15 +182,42 @@ else
end end
end end
metadata["color"][4] = static.alpha;
metadata["alpha"] = static.alpha;
-- set alphas -- set alphas
for k, v in pairs(children) do for k, v in pairs(children) do
if v:uid() <= 0 then goto continue end if v:uid() <= 0 then goto continue end
-- set alpha -- set alpha
local metadata = v:getComponent("Metadata") local metadata = v:getComponent("Metadata")
metadata["alpha"] = static.alpha 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
if metadata["clickable"] then
-- backup color
if selectableElementColors[index] == nil then
selectableElementColors[index] = metadata["color"]
end
-- 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)
local transform = v:getComponent("Transform") local transform = v:getComponent("Transform")
local static = Static.get(v) local static = Static.get(v)
@ -218,7 +231,6 @@ else
else else
static.delta = static.delta + time.delta() * 1.5 static.delta = static.delta + time.delta() * 1.5
transform.position = Vector3f.lerp( static.from, static.to, static.delta ) transform.position = Vector3f.lerp( static.from, static.to, static.delta )
v:setComponent("Metadata", metadata)
end end
::continue:: ::continue::
@ -249,4 +261,3 @@ else
transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time ) transform.orientation = Quaternion.axisAngle( Vector3f(0, 0, 1), static.time )
end end
end ) end )
end

View File

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

View File

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

View File

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

View File

@ -11,12 +11,12 @@
// exact matches // exact matches
"worldspawn": { "worldspawn": {
"physics": { "type": "mesh", "static": true }, "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 }, "optimize meshlets": { "simplify": 0.125, "print": false },
"unwrap mesh": true "unwrap mesh": true
}, },
"worldspawn_skybox": { "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 }, "optimize meshlets": { "simplify": 0.125, "print": false },
"unwrap mesh": true "unwrap mesh": true
}, },

View File

@ -4,16 +4,16 @@
// { "filename": "./models/ss2_medsci1.glb" } // { "filename": "./models/ss2_medsci1.glb" }
// { "filename": "./models/ss2_medsci1/graph.json" } // { "filename": "./models/ss2_medsci1/graph.json" }
// { "filename": "./models/ss2_medsci1_small.glb" } // { "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.glb" }
// { "filename": "./models/ss2_medsci1_smallish/graph.json" } { "filename": "./models/ss2_medsci1_smallish/graph.json" }
], ],
"metadata": { "metadata": {
"graph": { "graph": {
"bgm": "./audio/music/medsci1.ogg", "bgm": "./audio/music/medsci1.ogg",
"tags": { "tags": {
"/^prop_/": { "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_/": { "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": { "graph": {
"initial buffer elements": 128 "initial buffer elements": 256
}, },
"ext": { "ext": {
"opengl": { "opengl": {

View File

@ -1 +1 @@
opengl vulkan

View File

@ -6,3 +6,14 @@ Separate per-target copies of dependencies may exist here as well, such as:
* Dreamcast * 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 render();
void UF_API destroy( bool soft = false ); void UF_API destroy( bool soft = false );
void UF_API initialize( uf::Object& ); void UF_API initialize( uf::Object&, size_t = uf::graph::initialBufferElements );
void UF_API initialize( pod::Graph::Storage& ); void UF_API initialize( pod::Graph::Storage&, size_t = uf::graph::initialBufferElements );
void UF_API tick( uf::Object& ); void UF_API tick( uf::Object& );
void UF_API tick( pod::Graph::Storage& ); void UF_API tick( pod::Graph::Storage& );
void UF_API render( uf::Object& ); void UF_API render( uf::Object& );

View File

@ -328,21 +328,40 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
int mouseY = payload.mouse.position.y; int mouseY = payload.mouse.position.y;
} }
metadata.ui.click.ed = clicked; 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 ) { if ( clicked ) {
this->callHook("gui:Clicked.%UID%", payload); this->callHook("gui:Clicked.%UID%", payload);
} }
this->callHook("gui:Mouse.Clicked.%UID%", payload); this->callHook("gui:Mouse.Clicked.%UID%", payload);
} ); } );
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){ this->addHook( "gui:Clicked.%UID%", [&](ext::json::Value& json){
if ( ext::json::isNull( json ) ) return;
pod::payloads::windowMouseClick payload; pod::payloads::windowMouseClick payload;
this->callHook("gui:Clicked.%UID%", payload); this->callHook("gui:Clicked.%UID%", payload);
}); });
this->addHook( "gui:Clicked.%UID%", [&](pod::payloads::windowMouseClick& 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"] ) ) { if ( ext::json::isObject( metadataJson["events"]["click"] ) ) {
ext::json::Value event = metadataJson["events"]["click"]; ext::json::Value event = metadataJson["events"]["click"];
metadataJson["events"]["click"] = ext::json::array(); metadataJson["events"]["click"] = ext::json::array();
@ -399,35 +418,57 @@ void ext::GuiBehavior::initialize( uf::Object& self ) {
int mouseY = payload.mouse.position.y; int mouseY = payload.mouse.position.y;
} }
metadata.ui.hover.ed = hovered; uf::Serializer jsonPayload = ext::json::null();
if ( hovered && hoverTimer.elapsed().asDouble() >= 1 ) { // to-do: do something about trying to trigger json-bound hooks
hoverTimer.reset(); 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); this->callHook("gui:Hovered.%UID%", payload);
} }
metadata.ui.hover.ed = hovered;
this->callHook("gui:Mouse.Moved.%UID%", payload); 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){ 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"] ) ) { if ( ext::json::isObject( metadataJson["events"]["hover"] ) ) {
ext::json::Value event = 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; metadataJson["events"]["hover"][0] = event;
} else if ( !ext::json::isArray( metadataJson["events"]["hover"] ) ) { } 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; return;
} }
for ( int i = 0; i < metadataJson["events"]["hover"].size(); ++i ) { for ( int i = 0; i < metadataJson["events"]["hover"].size(); ++i ) {
ext::json::Value event = metadataJson["events"]["hover"][i]; ext::json::Value event = metadataJson["events"]["hover"][i];
ext::json::Value payload = event["payload"]; ext::json::Value payload = event["payload"];
float delay = event["delay"].as<float>(); if ( event["delay"].is<float>() ) {
if ( event["delay"].is<double>() ) {
this->queueHook(event["name"].as<uf::stl::string>(), payload, event["delay"].as<float>()); this->queueHook(event["name"].as<uf::stl::string>(), payload, event["delay"].as<float>());
} else { } else {
this->callHook(event["name"].as<uf::stl::string>(), payload ); 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 ); uf::transform::rotate( cameraTransform, cameraTransform.right, lookDelta.y );
} else metadata.camera.limit.current.y -= lookDelta.y; } else metadata.camera.limit.current.y -= lookDelta.y;
} }
} else { } else if ( metadata.system.control ) {
if ( keys.lookRight ^ keys.lookLeft ) { if ( keys.lookRight ^ keys.lookLeft ) {
if ( collider.body ) uf::physics::impl::applyRotation( collider, transform.up, speed.rotate * (keys.lookRight ? 1 : -1) ); else 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) ); 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; size_t uf::graph::initialBufferElements = 1024;
#endif
bool uf::graph::globalStorage; bool uf::graph::globalStorage;
pod::Graph::Storage uf::graph::storage; 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 ); if ( uf::graph::globalStorage ) return uf::graph::initialize( uf::graph::storage );
return uf::graph::initialize( uf::scene::getCurrentScene() ); return uf::graph::initialize( uf::scene::getCurrentScene() );
} }
void uf::graph::initialize( uf::Object& object ) { void uf::graph::initialize( uf::Object& object, size_t initialElements ) {
return uf::graph::initialize( object.getComponent<pod::Graph::Storage>() ); 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.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 ); // to-do: check if opengl really needs these
storage.buffers.instance.initialize( (const void*) nullptr, sizeof(pod::Instance) * uf::graph::initialBufferElements, uf::renderer::enums::Buffer::STORAGE ); storage.buffers.drawCommands.initialize( (const void*) nullptr, sizeof(pod::DrawCommand) * initialElements, 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.instance.initialize( (const void*) nullptr, sizeof(pod::Instance) * initialElements, 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.instanceAddresses.initialize( (const void*) nullptr, sizeof(pod::Instance::Addresses) * initialElements, 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.joint.initialize( (const void*) nullptr, sizeof(pod::Matrix4f) * initialElements, 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.material.initialize( (const void*) nullptr, sizeof(pod::Material) * initialElements, uf::renderer::enums::Buffer::STORAGE );
storage.buffers.light.initialize( (const void*) nullptr, sizeof(pod::Light) * uf::graph::initialBufferElements, 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() { void uf::graph::tick() {
if ( uf::graph::globalStorage ) return uf::graph::tick( uf::graph::storage ); 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 ) { void uf::graph::reload( pod::Graph& graph, pod::Node& node ) {
if ( !(0 <= node.mesh && node.mesh < graph.meshes.size()) ) return; if ( !(0 <= node.mesh && node.mesh < graph.meshes.size()) ) return;
if ( !node.entity ) return;
auto& scene = uf::scene::getCurrentScene(); auto& scene = uf::scene::getCurrentScene();
auto& storage = uf::graph::globalStorage ? uf::graph::storage : scene.getComponent<pod::Graph::Storage>(); 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 // disable if not tagged for streaming
// to-do: check tag // to-do: check tag
if ( node.name != graph.settings.stream.tag ) { if ( graph.settings.stream.tag != "" && node.name != graph.settings.stream.tag ) {
radius = 0; 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 ) { if ( needle == key ) {
exact = value; exact = value;
breaks = true; 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; regexed = value;
} }
}); });

View File

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

View File

@ -217,7 +217,7 @@ void ext::opengl::initialize() {
renderMode->initialize(device); renderMode->initialize(device);
} }
uf::graph::initialize(); // uf::graph::initialize();
auto tasks = uf::thread::schedule(settings::invariant::multithreadedRecording); auto tasks = uf::thread::schedule(settings::invariant::multithreadedRecording);
for ( auto& renderMode : renderModes ) { if ( !renderMode ) continue; 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) {} 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 ) { 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); ::keyboard.device = maple_enum_type(1, MAPLE_FUNC_KEYBOARD);
this->setSize(_size); this->setSize(_size);

View File

@ -4,7 +4,7 @@ CC = gcc
CXX = $(KOS_CCPLUS) CXX = $(KOS_CCPLUS)
RENDERER = opengl RENDERER = opengl
TARGET_EXTENSION = elf 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 WARNINGS = -Wno-attributes -Wno-conversion-null
FLAGS += $(KOS_CPPFLAGS) -m4-single -std=c++17 $(OPTIMIZATIONS) $(WARNINGS) -fdiagnostics-color=always 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 INCS += $(KOS_INC_PATHS) -I/opt/dreamcast/sh-elf/sh-elf/include