xurfer/ext/http-lovr moves http.request to its own thread

This commit is contained in:
Leon van Kammen 2026-06-22 16:44:07 +02:00
parent 6cfdefe050
commit 497960163a
8 changed files with 135 additions and 23 deletions

View file

@ -1,6 +1,8 @@
#!/bin/sh #!/bin/sh
export DEBUG=1
cd xurfer cd xurfer
#appimage-run ~/apps/lovr*.AppImage . test.glb runtime="/home/leon/apps/lovr-v0.18.0-x86_64.AppImage"
#appimage-run ~/apps/lovr*.AppImage . https://snips.sh/f/_U5-XctEVE?r=1 #appimage-run $runtime . test.glb
#appimage-run ~/apps/lovr*.AppImage . http://localhost:8080/simple-a.glb appimage-run $runtime . https://snips.sh/f/_U5-XctEVE?r=1
appimage-run ~/apps/lovr*.AppImage . http://localhost:8080/level3-4-playaudio.glb #appimage-run $runtime . http://localhost:8080/simple-a.glb
#appimage-run $runtime . http://localhost:8080/level3-4-playaudio.glb

View file

@ -6,8 +6,19 @@ ecs.init = function()
baseEntify.filter = ecs.rejectAll('commit') baseEntify.filter = ecs.rejectAll('commit')
baseEntify.updatethread = true baseEntify.updatethread = true
function baseEntify:onAdd(obj) function baseEntify:onAdd(obj)
obj.commit = api.util.commit( api.world, obj, api ) obj.commit = api.util.commit( api.world, obj, api )
api.world.commit = api.util.commit( api.world, false, api) api.world.commit = api.util.commit( api.world, false, api)
api.world.get = function(filter)
local found = false
foreach( api.world.entities, function(k,obj)
if type(obj) == 'table' then
if filter(obj) then found = obj end
end
end)
return found
end
end end
ecs.addSystem( api.world, baseEntify ) ecs.addSystem( api.world, baseEntify )
end end
@ -15,7 +26,6 @@ end
ecs.clear = function() ecs.clear = function()
api.ext.exec("onClear") api.ext.exec("onClear")
api.ecs.clearEntities( api.world ) api.ecs.clearEntities( api.world )
api.ecs.update( api.world )
api.world.commit() api.world.commit()
end end

View file

@ -22,6 +22,7 @@ return {
obj = obj or {} obj = obj or {}
print("[i] loading URL: " .. URI.url) print("[i] loading URL: " .. URI.url)
obj.URL = api.url.parse(URI.url) obj.URL = api.url.parse(URI.url)
trace(obj.URL)
obj.URLResponse = {} obj.URLResponse = {}
local protocol = "file" local protocol = "file"
foreach( api.protocol, function(name, p) foreach( api.protocol, function(name, p)
@ -33,7 +34,8 @@ return {
if obj.URL.protocol == 'file' then if obj.URL.protocol == 'file' then
return obj.commit('onURI') return obj.commit('onURI')
end end
local status, data, headers = protocol.request(URI.url) protocol.request(URI.url, nil,
function(status,data,headers)
obj.URLResponse = { obj.URLResponse = {
ok = (status ~= nil and status >= 200 and status < 300), ok = (status ~= nil and status >= 200 and status < 300),
status = status, status = status,
@ -43,6 +45,8 @@ return {
api.ecs.add( api.world, obj ) api.ecs.add( api.world, obj )
obj.commit('onURI') obj.commit('onURI')
end end
)
end
end end
} }

View file

@ -0,0 +1,76 @@
-- this extension will make network requests run async in LoVR runtime
local api = ...
local ecs = api.ecs
local http
http = {
name = "http-lovr",
enabled = true,
cache = {},
init = function()
if lovr ~= nil then
http.initThreadLovr()
api.protocol.http = {
request = function(url,opts,cb)
http.cache[ url ] = { url = url, opts = opts }
http.channel:push( http.cache[url] ) -- send to thread
http.cache[ url ].cb = cb -- stick callback to req
end
}
end
end,
initThreadLovr = function()
threadCode = [[
local lovr = require("lovr")
require 'lovr.filesystem'
local util = require("util")
local http = require("http")
lovr.thread = require 'lovr.thread'
lovr.timer = require 'lovr.timer'
local channel = lovr.thread.getChannel('http')
while true do
local req, present = channel:peek()
if present and req.status == nil then
req.status = -1
local status, data, headers = http.request(req.url)
req.status = status
req.data = data
req.headers = headers
channel:pop() -- remove last
channel:push(req) -- re-push but with status (which thread ignores)
lovr.timer.sleep(.1)
end
end
]]
http.channel = lovr.thread.getChannel('http')
http.thread = lovr.thread.newThread(threadCode)
http.thread:start()
end,
update = function()
if lovr ~= nil then
local msg
local res, present = http.channel:peek()
if present and res.status ~= nil then
msg = http.channel:pop()
-- we got our urlrequest response
if http.cache[ msg.url ] then
http.cache[ msg.url ].cb( msg.status, msg.data, msg.headers )
http.cache[ msg.url ] = nil
else
error("msg-object was not found in cache")
end
end
end
end,
}
return http

View file

@ -19,9 +19,9 @@ xrfimpl = {
trace("\n[xrf] href was clicked: " .. href) trace("\n[xrf] href was clicked: " .. href)
api.ext.exec("onClick", obj, collider) api.ext.exec("onClick", obj, collider)
--local ok = xrf.level2.load( href, obj, api.ext.URI.current, xrfimpl.onLoad) local ok = xrf.level2.load( href, obj, api.ext.URI.current, xrfimpl.onLoad)
-- or or
-- xrf.level4.import( href, obj, api.ext.URI.current, xrfimpl.onImport) xrf.level4.import( href, obj, api.ext.URI.current, xrfimpl.onImport)
end end
) )
@ -48,8 +48,17 @@ xrfimpl = {
onImport = function(absoluteHref,obj) onImport = function(absoluteHref,obj)
trace("[xrf.level4] ! (import) operator found") trace("[xrf.level4] ! (import) operator found")
local filter = function(o)
return (o.URL ~= nil and o.URL.URN:gsub("#.*","") == absoluteHref:gsub("#.*",""))
end
local obj = api.world.get(filter)
if obj then
obj.URL = url.parse(absoluteHref) -- re-parse to detect media fragment change
xrfimpl.onAudioFile(obj)
else
api.ecs.add( api.world, { URI = { url = absoluteHref, method = 'GET', target = obj } }) api.ecs.add( api.world, { URI = { url = absoluteHref, method = 'GET', target = obj } })
end end
end
} }

View file

@ -18,6 +18,7 @@
-- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
-- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -- ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
-- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -- NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
--
-- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -- ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- --
-- The views and conclusions contained in the software and documentation are those of the -- The views and conclusions contained in the software and documentation are those of the
@ -145,15 +146,19 @@ function url.parse(url_string)
return URI return URI
end end
url.getAbsolute = function(href,referer) url.getAbsolute = function(href,referer,keephash)
local URL = url.parse(href) local URL = url.parse(href)
local URLref = url.parse(referer) local URLref = url.parse(referer)
if referer ~= nil and URL.protocol == 'file' and URLref.protocol ~= 'file' then if referer ~= nil and URL.protocol == 'file' and URLref.protocol ~= 'file' then
URL.protocol = URLref.protocol URL.protocol = URLref.protocol
URL.domain = URLref.domain URL.domain = URLref.domain
URL.string = URLref.protocol .. '://' .. URLref.domain .. '/' .. URL.path .. URL.hash URL.string = URLref.protocol .. '://' .. URLref.domain .. '/' .. URL.path .. URL.hash
if keephash then
URL.URN = URL.string
else
URL.URN = URL.string:gsub("#.*", "") URL.URN = URL.string:gsub("#.*", "")
end end
end
return URL return URL
end end

View file

@ -14,7 +14,14 @@ backend.resourcePath = "lovr/" .. backend.resourcePath
api.iui = iui api.iui = iui
api.backend = backend api.backend = backend
-- decorate api -- decorate api
api.protocol.http = require('http') local http = require('http')
api.protocol.http = {
request = function(url,opts,cb)
local status, data, headers = http.request(url,opts)
if cb == nil then print("error: api.protocol.http.request called without callback") end
cb(status,data,headers)
end
}
local ecs = api.ecs local ecs = api.ecs
ecs.filterDraw = ecs.requireAll('drawthread') ecs.filterDraw = ecs.requireAll('drawthread')
ecs.filterUpdate = ecs.requireAll('updatethread') ecs.filterUpdate = ecs.requireAll('updatethread')
@ -49,7 +56,6 @@ function lovr.load()
end end
function lovr.update(dt) function lovr.update(dt)
ecs.update( api.world, dt, ecs.filterUpdate ) ecs.update( api.world, dt, ecs.filterUpdate )
iui.beginFrame(dt) iui.beginFrame(dt)

View file

@ -117,7 +117,7 @@ function util.traverseXML( parsedXml, cb)
return function() return parent end return function() return parent end
end)(parent) end)(parent)
end end
cb(struct,raw) cb(struct,node)
end, end,
'___children' '___children'
) )