1
0
Fork 0
mirror of https://github.com/Reuh/ubiquitousse.git synced 2025-10-27 09:09:30 +00:00

uqt.signal

This commit is contained in:
Étienne Fildadut 2019-12-27 18:54:30 +01:00
parent 82bc7268e6
commit f6fb8ad649
11 changed files with 331 additions and 80 deletions

View file

@ -2,18 +2,30 @@ local uqt = require((...):match("^(.-ubiquitousse)%."))
local ctr = require("ctr")
local gfx = require("ctr.gfx")
local function checkCompat(stuffName, expectedVersion, actualVersion)
if actualVersion ~= expectedVersion then
local txt = ("Ubiquitousse ctrµLua backend was made for %s %s but %s is used!\nThings may not work as expected.")
:format(stuffName, expectedVersion, actualVersion)
print(txt)
for _=0,300 do
gfx.start(gfx.TOP)
gfx.wrappedText(0, 0, txt, gfx.TOP_WIDTH)
gfx.stop()
gfx.render()
end
local madeForCtr = "v1.0"
local madeForUqt = "0.0.1"
-- Check versions
local txt = ""
if ctr.version ~= madeForCtr then
txt = txt .. ("Ubiquitousse ctrµLua backend was made for ctrµLua %s but %s is used!\n")
:format(madeForCtr, uqt.version)
end
if uqt.version ~= madeForUqt then
txt = txt .. ("Ubiquitousse ctrµLua backend was made for Ubiquitousse %s but %s is used!\n")
:format(madeForUqt, uqt.version)
end
-- Show warnings
if txt ~= "" then
txt = txt .. "Things may not work as expected.\n"
print(txt)
for _=0,300 do
gfx.start(gfx.TOP)
gfx.wrappedText(0, 0, txt, gfx.TOP_WIDTH)
gfx.stop()
gfx.render()
end
end
checkCompat("ctrµLua", "v1.0", ctr.version) -- not really a version, just get the latest build
checkCompat("Ubiquitousse", "0.0.1", uqt.version)

View file

@ -1,11 +1,30 @@
local uqt = require((...):match("^(.-ubiquitousse)%."))
local function checkCompat(stuffName, expectedVersion, actualVersion)
if actualVersion ~= expectedVersion then
local txt = ("Ubiquitousse Löve backend was made for %s %s but %s is used!\nThings may not work as expected.")
:format(stuffName, expectedVersion, actualVersion)
print(txt)
local madeForLove = { 11, "x", "x" }
local madeForUqt = "0.0.1"
-- Check versions
local txt = ""
local actualLove = { love.getVersion() }
for i, v in ipairs(madeForLove) do
if v ~= "x" then
if actualLove[i] ~= v then
txt = txt .. ("Ubiquitousse Löve backend was made for LÖVE %s.%s.%s but %s.%s.%s is used!\n")
:format(madeForLove[1], madeForLove[2], madeForLove[3], actualLove[1], actualLove[2], actualLove[3])
break
end
end
end
checkCompat("Löve", "11.3.0", ("%s.%s.%s"):format(love.getVersion()))
checkCompat("Ubiquitousse", "0.0.1", uqt.version)
if uqt.version ~= madeForUqt then
txt = txt .. ("Ubiquitousse Löve backend was made for Ubiquitousse %s but %s is used!\n")
:format(madeForUqt, uqt.version)
end
-- Show warnings
if txt ~= "" then
txt = txt .. "Things may not work as expected.\n"
print(txt)
love.window.showMessageBox("Compatibility warning", txt, "warning")
end

View file

@ -64,33 +64,19 @@ local ubiquitousse
ubiquitousse = {
--- Ubiquitousse version.
-- @impl ubiquitousse
version = "0.0.1",
--- Should be called each time the game loop is ran; will update every loaded Ubiquitousse module that needs it.
-- @tparam number dt time since last call, in miliseconds
-- @impl mixed
update = function(dt)
if ubiquitousse.timer then ubiquitousse.timer.update(dt) end
if ubiquitousse.scene then ubiquitousse.scene.update(dt) end
if ubiquitousse.input then ubiquitousse.input.update(dt) end
end,
--- Should be called each time the game expect a new frame to be drawn; will draw every loaded Ubiquitousse module that needs it
-- The screen is expected to be cleared since last frame.
-- @impl mixed
draw = function()
if ubiquitousse.scene then ubiquitousse.scene.draw() end
end
version = "0.0.1"
}
-- We're going to require modules requiring Ubiquitousse, so to avoid stack overflows we already register the ubiquitousse package
package.loaded[p] = ubiquitousse
-- Require external submodules
for _, m in ipairs{"asset", "ecs", "input", "scene", "timer", "util"} do
for _, m in ipairs{"signal", "asset", "ecs", "input", "scene", "timer", "util"} do
local s, t = pcall(require, p.."."..m)
if s then
ubiquitousse[m] = t
elseif not t:match("^module [^n]+ not found") then
error(t)
end
end

View file

@ -1,5 +1,8 @@
local input = require((...):match("^(.-%.)backend").."input")
local loaded, signal = pcall(require, (...):match("^(.-)input").."signal")
if not loaded then signal = nil end
local gfx = require("ctr.gfx")
local hid = require("ctr.hid")
@ -266,4 +269,9 @@ input.default.pointer:bind(
input.default.confirm:bind("key.a")
input.default.cancel:bind("key.b")
--- Register signals
if signal then
signal.event:replace("update", oUpdate, input.update)
end
return input

View file

@ -1,5 +1,8 @@
local input = require((...):match("^(.-%.)backend").."input")
local loaded, signal = pcall(require, (...):match("^(.-)input").."signal")
if not loaded then signal = nil end
-- Config --
-- Use ScanCodes (layout independant input) instead of KeyConstants (layout dependant) for keyboard input
@ -13,24 +16,23 @@ local displayKeyConstant = true
love.mouse.setVisible(false)
-- Button detection
-- FIXME love callbacks do something cleaner
local buttonsInUse = {}
local axesInUse = {}
function love.keypressed(key, scancode, isrepeat)
function input.keypressed(key, scancode, isrepeat)
if useScancodes then key = scancode end
buttonsInUse["keyboard."..key] = true
end
function love.keyreleased(key, scancode)
function input.keyreleased(key, scancode)
if useScancodes then key = scancode end
buttonsInUse["keyboard."..key] = nil
end
function love.mousepressed(x, y, button, istouch)
function input.mousepressed(x, y, button, istouch)
buttonsInUse["mouse."..button] = true
end
function love.mousereleased(x, y, button, istouch)
function input.mousereleased(x, y, button, istouch)
buttonsInUse["mouse."..button] = nil
end
function love.wheelmoved(x, y)
function input.wheelmoved(x, y)
if y > 0 then
buttonsInUse["mouse.wheel.up"] = true
elseif y < 0 then
@ -42,17 +44,17 @@ function love.wheelmoved(x, y)
buttonsInUse["mouse.wheel.left"] = true
end
end
function love.mousemoved(x, y, dx, dy)
function input.mousemoved(x, y, dx, dy)
if dx ~= 0 then axesInUse["mouse.move.x"] = dx/love.graphics.getWidth() end
if dy ~= 0 then axesInUse["mouse.move.y"] = dy/love.graphics.getHeight() end
end
function love.gamepadpressed(joystick, button)
function input.gamepadpressed(joystick, button)
buttonsInUse["gamepad.button."..joystick:getID().."."..button] = true
end
function love.gamepadreleased(joystick, button)
function input.gamepadreleased(joystick, button)
buttonsInUse["gamepad.button."..joystick:getID().."."..button] = nil
end
function love.gamepadaxis(joystick, axis, value)
function input.gamepadaxis(joystick, axis, value)
if value ~= 0 then
axesInUse["gamepad.axis."..joystick:getID().."."..axis] = value
else
@ -61,11 +63,7 @@ function love.gamepadaxis(joystick, axis, value)
end
-- Windows size
input.drawWidth, input.drawHeight = love.graphics.getWidth(), love.graphics.getHeight()
function love.resize(width, height)
input.drawWidth = width
input.drawHeight = height
end
input.getDrawWidth, input.getDrawHeight = love.graphics.getWidth, love.graphics.getHeight
-- Update
local oUpdate = input.update
@ -190,7 +188,7 @@ input.basicAxisDetector = function(id)
end
end
input.buttonsInUse = function(threshold)
input.buttonUsed = function(threshold)
local r = {}
threshold = threshold or 0.5
for b in pairs(buttonsInUse) do
@ -201,10 +199,10 @@ input.buttonsInUse = function(threshold)
table.insert(r, b.."%"..(v < 0 and -threshold or threshold))
end
end
return r
return unpack(r)
end
input.axesInUse = function(threshold)
input.axisUsed = function(threshold)
local r = {}
threshold = threshold or 0.5
for b,v in pairs(axesInUse) do
@ -212,7 +210,7 @@ input.axesInUse = function(threshold)
table.insert(r, b.."%"..threshold)
end
end
return r
return unpack(r)
end
input.buttonName = function(...)
@ -316,4 +314,18 @@ input.default.cancel:bind(
"gamepad.button.1.b"
)
--- Register signals
if signal then
signal.event:bind("keypressed", input.keypressed)
signal.event:bind("keyreleased", input.keyreleased)
signal.event:bind("mousepressed", input.mousepressed)
signal.event:bind("mousereleased", input.mousereleased)
signal.event:bind("wheelmoved", input.wheelmoved)
signal.event:bind("mousemoved", input.mousemoved)
signal.event:bind("gamepadpressed", input.gamepadpressed)
signal.event:bind("gamepadreleased", input.gamepadreleased)
signal.event:bind("gamepadaxis", input.gamepadaxis)
signal.event:replace("update", oUpdate, input.update)
end
return input

View file

@ -1,5 +1,8 @@
--- ubiquitousse.input
-- Depends on a backend.
-- Optional dependencies: ubiquitousse.signal (to bind to update signal in signal.event)
local loaded, signal = pcall(require, (...):match("^(.-)input").."signal")
if not loaded then signal = nil end
-- TODO: some key selection helper? Will be backend-implemented, to account for all the possible input methods.
-- TODO: some way to list all possible input / outputs, or make the *inUse make some separation between inputs indiscutitably in use and those who are incertain.
@ -40,8 +43,8 @@ local button_mt = {
-- @treturn ButtonInput this ButtonInput object
unbind = function(self, ...)
for _, d in ipairs({...}) do
for i, detector in ipairs(self.detectors) do
if detector == d then
for i=#self.detectors, 1, -1 do
if self.detectors[i] == d then
table.remove(self.detectors, i)
break
end
@ -270,7 +273,9 @@ local axis_mt = {
self.val, self.raw, self.max = val, raw, max
updated[self] = true
end
end
end,
--- LÖVE note: other callbacks that are defined in backend/love.lua and need to be called in the associated LÖVE callbacks.
}
axis_mt.__index = axis_mt
@ -382,9 +387,9 @@ local pointer_mt = {
x = function(self)
if self.grabbing == self then
self:update()
return self.valX + (self.offsetX or self.width or input.drawWidth/2)
return self.valX + (self.offsetX or self.width or input.getDrawWidth()/2)
else
return self.offsetX or self.width or input.drawWidth/2
return self.offsetX or self.width or input.getDrawWidth()/2
end
end,
--- Returns the current Y value of the pointer.
@ -392,9 +397,9 @@ local pointer_mt = {
y = function(self)
if self.grabbing == self then
self:update()
return self.valY + (self.offsetY or self.height or input.drawHeight/2)
return self.valY + (self.offsetY or self.height or input.getDrawHeight()/2)
else
return self.offsetY or self.height or input.drawHeight/2
return self.offsetY or self.height or input.getDrawHeight()/2
end
end,
@ -418,9 +423,9 @@ local pointer_mt = {
local magnitude = sqrt(x*x + y*y)
cx, cy = cx / magnitude * width, cy / magnitude * height
end
return cx + (self.offsetX or width or input.drawWidth/2), cy + (self.offsetY or height or input.drawHeight/2)
return cx + (self.offsetX or width or input.getDrawWidth()/2), cy + (self.offsetY or height or input.getDrawHeight()/2)
else
return self.offsetX or width or input.drawWidth/2, self.offsetY or height or input.drawHeight/2
return self.offsetX or width or input.getDrawWidth()/2, self.offsetY or height or input.getDrawHeight()/2
end
end,
@ -445,7 +450,7 @@ local pointer_mt = {
if not updated[self] then
local x, y = self.valX, self.valY
local xSpeed, ySpeed = self.xSpeed, self.ySpeed
local width, height = self.width or input.drawWidth/2, self.height or input.drawHeight/2
local width, height = self.width or input.getDrawWidth()/2, self.height or input.getDrawHeight()/2
local newX, newY = x, y
local maxMovX, maxMovY = 0, 0 -- the maxium axis movement in a direction (used to determine which axes have the priority) (absolute value)
for _, pointer in ipairs(self.detectors) do
@ -642,16 +647,16 @@ input = {
--- Returns a list of the buttons currently in use, identified by their string button identifier.
-- This may also returns "axis threshold" buttons if an axis passes the threshold.
-- @treturn table<string> buttons identifiers list
-- @treturn[opt=0.5] number threshold the threshold to detect axes as button
-- @tparam[opt=0.5] number threshold the threshold to detect axes as button
-- @treturn string,... buttons identifiers list
-- @impl backend
buttonsInUse = function(threshold) end,
buttonUsed = function(threshold) end,
--- Returns a list of the axes currently in use, identified by their string axis identifier
-- @treturn table<string> axes identifiers list
-- @treturn[opt=0.5] number threshold the threshold to detect axes
-- @tparam[opt=0.5] number threshold the threshold to detect axes
-- @treturn string,... axes identifiers list
-- @impl backend
axesInUse = function(threshold) end,
axisUsed = function(threshold) end,
--- Returns a nice name for the button identifier.
-- Can be locale-depedant and stuff, it's only for display.
@ -686,14 +691,14 @@ input = {
cancel = nil -- Button: used to cancel something. Example binds: Escape, B button.
},
--- Draw area dimensions.
--- Get draw area dimensions.
-- Used for pointers.
-- @impl backend
drawWidth = 1,
drawHeight = 1,
getDrawWidth = function() return 1 end,
getDrawHeight = function() return 1 end,
--- Update all the Inputs.
-- Should be called at every game update; called by ubiquitousse.update.
-- Should be called at every game update. If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event.
-- The backend can hook into this function to to its input-related updates.
-- @tparam numder dt the delta-time
-- @impl ubiquitousse
@ -701,6 +706,11 @@ input = {
dt = newDt
updated = {}
end
--- If you use LÖVE, note that in order to provide every feature (especially key detection), several callbacks functions will
-- need to be called on LÖVE events. See backend/love.lua.
-- If ubiquitousse.signal is available, these callbacks will be bound to signals in signal.event (with the same name as the LÖVE
-- callbacks, minux the "love.").
}
-- Create default inputs
@ -708,4 +718,9 @@ input.default.pointer = input.pointer()
input.default.confirm = input.button()
input.default.cancel = input.button()
-- Bind signals
if signal then
signal.event:bind("update", input.update)
end
return input

View file

@ -1,5 +1,8 @@
--- ubiquitousse.scene
-- Optional dependencies: ubiquitousse.timer (to provide each scene a timer registry)
-- Optional dependencies: ubiquitousse.signal (to bind to update and draw signal in signal.event)
local loaded, signal = pcall(require, (...):match("^(.-)scene").."signal")
if not loaded then signal = nil end
local loaded, timer = pcall(require, (...):match("^(.-)scene").."timer")
if not loaded then timer = nil end
@ -134,8 +137,16 @@ scene = setmetatable({
table.remove(scene.stack)
end,
--- Pop all scenes.
-- @impl ubiquitousse
popAll = function()
while scene.current do
scene.pop()
end
end,
--- Update the current scene.
-- Should be called at every game update; called by ubiquitousse.update.
-- Should be called at every game update. If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event.
-- @tparam number dt the delta-time (milisecond)
-- @param ... arguments to pass to the scene's update function after dt
-- @impl ubiquitousse
@ -147,7 +158,7 @@ scene = setmetatable({
end,
--- Draw the current scene.
-- Should be called every time the game is draw; called by ubiquitousse.draw.
-- Should be called every time the game is draw. If ubiquitousse.signal is available, will be bound to the "draw" signal in signal.event.
-- @param ... arguments to pass to the scene's draw function
-- @impl ubiquitousse
draw = function(...)
@ -160,4 +171,10 @@ scene = setmetatable({
end
})
-- Bind signals
if signal then
signal.event:bind("update", scene.update)
signal.event:bind("draw", scene.draw)
end
return scene

43
signal/backend/love.lua Normal file
View file

@ -0,0 +1,43 @@
local signal = require((...):match("^(.-%.)backend").."signal")
function signal.registerEvents()
local callbacks = { -- everything except run, errorhandler, threaderror
"displayrotated", "draw", "load", "lowmemory", "quit", "update",
"directorydropped", "filedropped", "focus", "mousefocus", "resize", "visible",
"keypressed", "keyreleased", "textedited", "textinput",
"mousemoved", "mousepressed", "mousereleased", "wheelmoved",
"gamepadaxis", "gamepadpressed", "gamepadreleased",
"joystickadded", "joystickaxis", "joystickhat", "joystickpressed", "joystickreleased", "joystickremoved",
"touchmoved", "touchpressed", "touchreleased"
}
local event = signal.event
for _, callback in ipairs(callbacks) do
if callback == "update" then
if love[callback] then
local old = love[callback]
love[callback] = function(dt)
old(dt)
event:emit(callback, dt*1000)
end
else
love[callback] = function(dt)
event:emit(callback, dt*1000)
end
end
else
if love[callback] then
local old = love[callback]
love[callback] = function(...)
old(...)
event:emit(callback, ...)
end
else
love[callback] = function(...)
event:emit(callback, ...)
end
end
end
end
end
return signal

14
signal/init.lua Normal file
View file

@ -0,0 +1,14 @@
local signal
local p = ...
if love then
signal = require(p..".backend.love")
elseif package.loaded["ctr"] then
error("NYI")
elseif package.loaded["libretro"] then
error("NYI")
else
error("no backend for ubiquitousse.signal")
end
return signal

116
signal/signal.can Normal file
View file

@ -0,0 +1,116 @@
--- ubiquitousse.signal
let registry_mt = {
--- Map of signals to list of listeners.
-- @impl ubiquitousse
signals = {},
--- Bind one or several functions to a signal name.
-- @impl ubiquitousse
bind = :(name, fn, ...)
if not @signals[name] then
@signals[name] = {}
end
table.insert(@signals[name], fn)
if ... then
return @bind(name, ...)
end
end,
--- Unbind one or several functions to a signal name.
-- @impl ubiquitousse
unbind = :(name, fn, ...)
if not @signals[name] then
return
end
for i=#@signals[name], 1, -1 do
if @signals[name] == fn then
table.remove(@signals[name], i)
end
end
if ... then
return @unbind(name, ...)
end
end,
--- Remove every bound function to a signal name.
-- @impl ubiquitousse
unbindAll = :(name)
@signals[name] = nil
end,
--- Replace a bound function with another function.
-- @impl ubiquitousse
replace = :(name, sourceFn, destFn)
if not @signals[name] then
@signals[name] = {}
end
for i, fn in ipairs(@signals[name]) do
if fn == sourceFn then
@signals[name][i] = destFn
break
end
end
end,
--- Remove every bound function to every signal.
-- @impl ubiquitousse
clear = :()
@signals = {}
end,
--- Emit a signal, i.e. call every function bound to it, with the given arguments.
-- @impl ubiquitousse
emit = :(name, ...)
if @signals[name] then
for _, fn in ipairs(@signals[name]) do
fn(...)
end
end
end
}
registry_mt.__index = registry_mt
let signal = {
--- Creates and return a new SignalRegistry.
-- A SignalRegistry is a separate ubiquitousse.signal instance: its signals will be independant from other registries.
-- @impl ubiquitousse
new = ()
return setmetatable({ signals = {} }, registry_mt)
end,
--- Global SignalRegistry.
-- @impl ubiquitousse
signals = {},
bind = (...)
return registry_mt.bind(signal, ...)
end,
unbind = (...)
return registry_mt.unbind(signal, ...)
end,
clear = (...)
return registry_mt.clear(signal, ...)
end,
emit = (...)
return registry_mt.emit(signal, ...)
end,
--- SignalRegistry which will be used to bind signals that need to be called on game engine event.
-- For example, every ubiquitousse module with a "update" function will bind it to the "update" signal in the registry;
-- you can then call this signal on each game update to update every ubiquitousse module easily.
-- Provided signals:
-- * update, should be called on every game update
-- * draw, should be called on every game draw
-- * for LÖVE, there are callbacks for every LÖVE callback function that need to be called on their corresponding LÖVE callback
-- @impl mixed
event = nil,
--- Call this function to hook signal.event signals to the current backend.
-- For LÖVE, this means overriding every existing LÖVE callback. If a callback is already defined, the new one will call the old function along with the signal:emit.
-- @impl backend
registerEvents = () end
}
signal.event = signal.new()
return signal

View file

@ -1,5 +1,9 @@
--- ubiquitousse.timer
-- Depends on a backend.
-- Optional dependencies: ubiquitousse.signal (to bind to update signal in signal.event)
local loaded, signal = pcall(require, (...):match("^(.-)timer").."signal")
if not loaded then signal = nil end
local ease = require((...):match("^.-timer")..".easing")
local timer
@ -332,7 +336,7 @@ timer = {
-- @impl ubiquitousse
delayed = {},
lastTime = 0,
update = function(...)
update = function(...) -- If ubiquitousse.signal is available, will be bound to the "update" signal in signal.event.
return registry_mt.update(timer, ...)
end,
run = function(...)
@ -346,4 +350,9 @@ timer = {
end
}
-- Bind signals
if signal then
signal.event:bind("update", timer.update)
end
return timer