Compare commits

...

1 Commits

Author SHA1 Message Date
Leon van Kammen 9aff2133a0 improved performance on low-end smartphone
/ mirror_to_github (push) Successful in 19s Details
/ test (push) Successful in 3s Details
2024-10-04 15:49:15 +00:00
6 changed files with 142 additions and 100 deletions

View File

@ -40,15 +40,18 @@ if( typeof AFRAME != 'undefined '){
padding: { type: 'number',"default": 18 },
minimized: { type: 'boolean',"default":false},
maximized: { type: 'boolean',"default":false},
muteUntilPrompt:{ type: 'boolean',"default":true}, // mute stdout until a prompt is detected in ISO
HUD: { type: 'boolean',"default":false}, // link to camera movement
transparent: { type:'boolean', "default":false }, // need good gpu
xterm: { type: 'boolean', "default":true }, // use xterm.js? (=slower)
memory: { type: 'number', "default":48 }, // VM memory (in MB)
memory: { type: 'number', "default":64 }, // VM memory (in MB)
bufferLatency: { type: 'number', "default":300 }, // in ms: bufferlatency from webworker to xterm (batch-update every char to texture)
canvasLatency: { type: 'number', "default":300 } // in ms: time between canvas re-draws
},
init: function(){
this.el.object3D.visible = false
this.initHud()
fetch(this.data.iso,{method: 'HEAD'})
.then( (res) => {
if( res.status != 200 ) throw 'not found'
@ -267,6 +270,7 @@ if( typeof AFRAME != 'undefined '){
}
}
this.el.addEventListener('obbcollisionstarted', focus(false) )
this.el.sceneEl.addEventListener('enter-vr', focus(false) )
this.el.sceneEl.addEventListener('enter-ar', focus(false) )
this.el.sceneEl.addEventListener('exit-vr', focus(true) )
@ -275,6 +279,14 @@ if( typeof AFRAME != 'undefined '){
instance.object3D.quaternion.copy( AFRAME.scenes[0].camera.quaternion ) // face towards camera
},
initHud: function(){
if( AFRAME.utils.device.isMobile() ) this.data.HUD = true
if( this.data.HUD ){
document.querySelector('[camera]').appendChild( this.el )
this.el.setAttribute("position","0 -0.03 -0.4")
}
},
events:{
// combined AFRAME+DOM reactive events

View File

@ -17,12 +17,14 @@ function ISOTerminal(instance,opts){
ISOTerminal.prototype.emit = function(event,data,sender){
data = data || false
const evObj = new CustomEvent(event, {detail: data} )
//this.preventFrameDrop( () => {
// forward event to worker/instance/AFRAME element or component-function
// this feels complex, but actually keeps event- and function-names more concise in codebase
this.dispatchEvent( evObj )
if( sender != "instance" && this.instance ) this.instance.dispatchEvent(evObj)
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data})
if( sender != "worker" && this.worker ) this.worker.postMessage({event,data}, this.getTransferable(data) )
if( sender !== undefined && typeof this[event] == 'function' ) this[event].apply(this, data && data.push ? data : [data] )
//})
}
ISOTerminal.addEventListener = (event,cb) => {
@ -45,7 +47,9 @@ ISOTerminal.prototype.send = function(str, ttyNr){
}else this.emulator.keyboard_send_text(str) // vga screen
}else{
this.convert.toUint8Array( str ).map( (c) => {
this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
this.preventFrameDrop(
() => this.worker.postMessage({event:`serial${ttyNr}-input`,data:c})
)
})
}
}
@ -133,7 +137,6 @@ ISOTerminal.prototype.start = function(opts){
this.worker = new Worker("com/isoterminal/worker.js");
this.worker.onmessage = (e) => {
const xr = this.instance.sceneEl.renderer.xr
const {event,data} = e.data
const cb = (event,data) => () => {
if( data.promiseId ){
@ -141,12 +144,7 @@ ISOTerminal.prototype.start = function(opts){
}else this.emit(event,data,"worker") // forward event to world
}
// don't let workers cause framerate dropping
if( xr.isPresenting ){
xr.getSession().requestAnimationFrame(cb(event,data))
}else{
window.requestAnimationFrame(cb(event,data))
}
this.preventFrameDrop( cb(event,data) )
}
/*
@ -160,7 +158,7 @@ ISOTerminal.prototype.start = function(opts){
this.id = this.id == undefined ? 0 : this.id
data.id = this.id++
// Send id and task to WebWorker
this.worker.postMessage(data)
this.preventFrameDrop( () => this.worker.postMessage(data,getTransferable(data) ) )
return new Promise(resolve => this.resolvers[data.id] = resolve);
}.bind(this.worker.postMessage)
@ -196,12 +194,52 @@ ISOTerminal.prototype.start = function(opts){
'Transcending earthly limits'
]
const loadmsg = loading[ Math.floor(Math.random()*1000) % loading.length ] + "..(please wait..)"
const empower = [
"FOSS gives users control over their software, offering freedom to modify and share",
"Feeling powerless with tech? FOSS escapes a mindset known as learned helplessness",
"FOSS breaks this cycle by showing that anyone can learn and contribute",
"Proprietary software can make users dependent, but FOSS offers real choices",
"FOSS communities provide support and encourage users to develop new skills",
"Learned helplessness fades when we realize tech isnt too complex to understand",
"FOSS empowers users to customize and improve their tools",
"Engaging with FOSS helps build confidence and self-reliance in tech",
"FOSS tools are accessible and often better than closed alternatives",
"FOSS shows that anyone can shape the digital world with curiosity and effort",
"Linux can revive old computers, extending their life and reducing e-waste",
"Many lightweight Linux distributions run smoothly on older hardware",
"Installing Linux on aging devices keeps them functional instead of sending them to the landfill",
"Linux uses fewer resources, making it ideal for reusing older machines",
"By using Linux, you can avoid buying new hardware, cutting down on tech waste",
"Instead of discarding slow devices, Linux can bring them back to life",
"Linux supports a wide range of devices, helping to prevent e-waste",
"Open-source drivers in Linux enable compatibility with old peripherals, reducing the need for replacements",
"Free Linux software helps users avoid planned obsolescence in commercial products",
"Switching to Linux promotes sustainability by reducing demand for new gadgets and lowering e-waste"
]
const motd = `
\r . . ____ _____________ ________. ._. ._. . .
\r . . .\\ \\/ /\\______ \\/ _____// | \\. .
\r . . . \\ / | _/\\_____ \\/ ~ \\ .
\r . . . / \\ | | \\/ \\ Y / .
\r . . ./___/\\ \\ |____|_ /_______ /\\___|_ /. .
\r . . . . . .\\_/. . . . \\/ . . . .\\/ . . _ \\/ . .
\r https://xrsh.isvery.ninja ▬▬▬▬▬▬▬▬▬▬▬▬
\r local-first, polyglot, unixy WebXR IDE & runtime
\r
\r credits: NLnet | @nlnet@nlnet.nl
\r MrDoob | THREE.js
\r Diego Marcos | AFRAME.js
\r Leon van Kammen | @lvk@mastodon.online
\r Fabien Benetou | @utopiah@mastodon.pirateparty.be
`
const text_color = "\r"
const text_reset = "\033[0m"
const loadmsg = "\n\r "+loading[ Math.floor(Math.random()*1000) % loading.length ] + "..[please wait]"
const empowermsg = "\n\r "+text_reset+'"'+empower[ Math.floor(Math.random()*1000) % empower.length ] + '"\n\r'
this.emit('status',loadmsg)
this.emit('serial-output-string', text_color + loadmsg + text_reset + "\n\r")
this.emit('serial-output-string', motd + empowermsg + text_color + loadmsg + text_reset+"\n\r")
this.addEventListener('emulator-started', async (e) => {
@ -222,17 +260,25 @@ ISOTerminal.prototype.start = function(opts){
const str = e.detail
// lets scan for a prompt so we can send a 'ready' event to the world
if( !this.ready && str.match(/\n(\/ #|~%|\[.*\]>)/) ){
this.emit('postReady',{})
this.ready = true
setTimeout( () => this.emit('ready',{}), 500 )
}
if( this.ready ) this.emit('serial-output-string', e.detail )
if( !this.ready && str.match(/\n(\/ #|~%|\[.*\]>)/) ) this.postBoot()
if( this.ready || !this.opts.muteUntilPrompt ) this.emit('serial-output-string', e.detail )
})
});
}
ISOTerminal.prototype.postBoot = function(cb){
this.emit('postReady',{})
this.ready = true
setTimeout( () => {
this.emit('ready',{})
if( cb ) cb()
}, 500 )
}
// this is allows (unsophisticated) outputbuffering
ISOTerminal.prototype.bufferOutput = function(byte,cb,latency){
const resetBuffer = () => ({str:""})
this.buffer = this.buffer || resetBuffer()
@ -247,3 +293,26 @@ ISOTerminal.prototype.bufferOutput = function(byte,cb,latency){
}
}
ISOTerminal.prototype.preventFrameDrop = function(cb){
// don't let workers cause framerate dropping
const xr = this.instance.sceneEl.renderer.xr
if( xr.isPresenting ){
xr.getSession().requestAnimationFrame(cb)
}else{
window.requestAnimationFrame(cb)
}
}
ISOTerminal.prototype.getTransferable = function(data){
function isTransferable(obj) {
return obj instanceof ArrayBuffer ||
obj instanceof MessagePort ||
obj instanceof ImageBitmap ||
(typeof OffscreenCanvas !== 'undefined' && obj instanceof OffscreenCanvas) ||
(typeof ReadableStream !== 'undefined' && obj instanceof ReadableStream) ||
(typeof WritableStream !== 'undefined' && obj instanceof WritableStream) ||
(typeof TransformStream !== 'undefined' && obj instanceof TransformStream);
}
if( isTransferable(data) ) console.log("Transferable!")
if( isTransferable(data) ) return isTransferable(data) ? [data] : undefined
}

View File

@ -1,12 +1,14 @@
if( typeof emulator != 'undefined' ){
// inside worker-thread
this['emulator.restore_state'] = async function(){
await emulator.restore_state.apply(emulator, arguments[0])
this['emulator.restore_state'] = async function(data){
await emulator.restore_state(data)
console.log("restored state")
this.postMessage({event:"state_restored",data:false})
}
this['emulator.save_state'] = async function(){
let state = await emulator.save_state.apply(emulator, arguments[0])
console.log("saving session")
let state = await emulator.save_state()
this.postMessage({event:"state_saved",data:state})
}
@ -31,23 +33,8 @@ if( typeof emulator != 'undefined' ){
state = this.convert.base64ToArrayBuffer( stateBase64 )
this.addEventListener('state_restored', function(){
this.emit('postReady',e)
setTimeout( () => {
this.emit('ready',e)
// press CTRL+a l (=gnu screen redisplay)
setTimeout( () => this.send("l\n"),400 )
// reload index.js
this.emulator.read_file("root/index.js")
.then( this.convert.Uint8ArrayToString )
.then( this.runJavascript )
.catch( console.error )
// reload index.html
this.emulator.read_file("root/index.html")
.then( this.convert.Uint8ArrayToString )
.then( this.runHTML )
.catch( console.error )
}, 500 )
// simulate / fastforward boot events
this.postBoot( () => this.send("l\n") )
})
this.worker.postMessage({event:'emulator.restore_state',data:state})
@ -55,13 +42,13 @@ if( typeof emulator != 'undefined' ){
})
this.save = async () => {
const state = await this.worker.postMessage({event:"save_state",data:false})
console.log( String(this.convert.arrayBufferToBase64(state)).substr(0,5) )
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
const state = await this.worker.postMessage({event:"emulator.save_state",data:false})
}
this.addEventListener('state_saved', function(data){
debugger
this.addEventListener('state_saved', function(e){
const state = e.detail
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
console.log("state saved")
})
window.addEventListener("beforeunload", function (e) {

View File

@ -14,8 +14,7 @@ ISOTerminal.prototype.boot = async function(e){
if( this.serial_input == 0 ){
if( !this.noboot ){
let boot = "source /etc/profile\n"
this.send(boot+"\n")
this.send("source /etc/profile # \\o/ FOSS powa!\n")
}
}

View File

@ -1,34 +1,6 @@
importScripts("libv86.js");
importScripts("ISOTerminal.js") // we don't instance it again here (just use its functions)
//var emulator = new V86({
// wasm_path: "../build/v86.wasm",
// memory_size: 32 * 1024 * 1024,
// vga_memory_size: 2 * 1024 * 1024,
// bios: {
// url: "../bios/seabios.bin",
// },
// vga_bios: {
// url: "../bios/vgabios.bin",
// },
// cdrom: {
// url: "../images/linux4.iso",
// },
// autostart: true,
//});
//
//
//emulator.add_listener("serial0-output-byte", function(byte)
//{
// var chr = String.fromCharCode(byte);
// this.postMessage(chr);
//}.bind(this));
//
//this.onmessage = function(e)
//{
// emulator.serial0_send(e.data);
//};
this.runISO = function(opts){
if( opts.cdrom && !opts.cdrom.url.match(/^http/) ) opts.cdrom.url = "../../"+opts.cdrom.url
if( opts.bzimage && !opts.cdrom.url.match(/^http/) ) opts.bzimage.url = "../../"+opts.bzimage.url

View File

@ -103,8 +103,8 @@ AFRAME.registerComponent('xterm', {
init: function () {
const terminalElement = document.createElement('div')
terminalElement.setAttribute('style', `
width: ${Math.floor( window.innerWidth * 0.7 )}px;
height: ${Math.floor( window.innerHeight * 0.7 )}px;
width: 800px;
height: ${Math.floor( 800 * 0.527 )}px;
overflow: hidden;
`)
@ -133,6 +133,8 @@ AFRAME.registerComponent('xterm', {
return theme
}, {})
this.fontSize = 14
const term = this.term = new Terminal({
logLevel:"off",
theme: theme,
@ -141,12 +143,13 @@ AFRAME.registerComponent('xterm', {
disableStdin: false,
rows: this.data.rows,
cols: this.data.cols,
fontSize: 14,
fontSize: this.fontSize,
lineHeight: 1.15,
useFlowControl: true,
rendererType: this.renderType // 'dom' // 'canvas'
})
this.tick = AFRAME.utils.throttle( () => {
this.tick = AFRAME.utils.throttleLeadingAndTrailing( () => {
if( this.el.sceneEl.renderer.xr.isPresenting ){
// workaround
// xterm relies on window.requestAnimationFrame (which is not called WebXR immersive mode)
@ -165,7 +168,7 @@ AFRAME.registerComponent('xterm', {
const $screen = terminalElement.querySelector('.xterm-screen')
$screen.style.width = '100%'
term.on('refresh', AFRAME.utils.throttle( () => this.update(), 150 ) )
term.on('refresh', AFRAME.utils.throttleLeadingAndTrailing( () => this.update(), 150 ) )
term.on('data', (data) => {
this.el.emit('xterm-input', data)
})
@ -200,14 +203,14 @@ AFRAME.registerComponent('xterm', {
if( type == 'dom'){
this.el.dom.appendChild(this.el.terminalElement)
this.term.setOption('fontSize', 14 )
this.term.setOption('fontSize', this.fontSize )
this.term.setOption('rendererType',type )
this.renderType = type
}
if( type == 'canvas'){
this.el.appendChild(this.el.terminalElement)
this.term.setOption('fontSize', 48 )
this.term.setOption('fontSize', this.fontSize * 3 )
this.term.setOption('rendererType',type )
this.renderType = type
this.update()
@ -218,8 +221,8 @@ AFRAME.registerComponent('xterm', {
this.cursorCanvas = this.el.terminalElement.querySelector('.xterm-cursor-layer')
// Create a texture from the canvas
const canvasTexture = new THREE.Texture(this.canvas)
//canvasTexture.minFilter = THREE.LinearFilter
//canvasTexture.magFilter = THREE.LinearFilter
//canvasTexture.minFilter = THREE.NearestFilter //LinearFilter
//canvasTexture.magFilter = THREE.LinearMipMapLinearFilter //THREE.NearestFilter //LinearFilter
canvasTexture.needsUpdate = true; // Ensure the texture updates
let plane = this.el.planeText.getObject3D("mesh") //this.el.getObject3D('mesh')
if( plane.material ) plane.material.dispose()