added restore + requestAnimationFrameXR to mitigate requestAnimationFrame issues
/ test (push) Successful in 5s Details

This commit is contained in:
Leon van Kammen 2024-09-17 16:59:38 +00:00
parent 10c274fdd4
commit f75f34d6a6
10 changed files with 177 additions and 33 deletions

View File

@ -1,3 +1,5 @@
if( AFRAME.components.codemirror ) delete AFRAME.components.codemirror
AFRAME.registerComponent('codemirror', {
schema: {
foo: { type:"string"}
@ -16,7 +18,6 @@ AFRAME.registerComponent('codemirror', {
requires:{
window: "com/window.js",
htmltexture: "com/html-as-texture-in-xr.js",
codemirror: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.58.1/codemirror.js",
codemirrorcss: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.35.0/codemirror.css",
cmtheme: "https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.35.0/theme/shadowfox.css"
@ -31,6 +32,13 @@ AFRAME.registerComponent('codemirror', {
css: (me) => `.codemirror{
width:100%;
}
.codemirror *{
font-size: 14px;
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
font-weight:500 !important;
letter-spacing: 0 !important;
text-shadow: 0px 0px 10px #F075;
}
.wb-body + .codemirror{ overflow:hidden; }
.CodeMirror {
margin-top:18px;
@ -47,7 +55,6 @@ AFRAME.registerComponent('codemirror', {
DOMready: function(e){
console.log(`title: codemirror; uid: ${this.el.dom.id}; attach: #overlay; dom: #${this.el.dom.id};`)
this.el.setAttribute("window", `title: codemirror; uid: ${this.el.dom.id}; attach: #overlay; dom: #${this.el.dom.id};`)
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) // only show aframe-html in xr
this.editor = CodeMirror( this.el.dom, {
value: "function myScript(){return 100;}\n",
mode: "javascript",
@ -61,6 +68,9 @@ AFRAME.registerComponent('codemirror', {
}
})
this.editor.setOption("theme", "shadowfox")
setTimeout( () => {
this.el.setAttribute("html-as-texture-in-xr", `domid: #${this.el.dom.id}`) // only show aframe-html in xr
},1500)
},
},

View File

@ -36,6 +36,10 @@ if( !AFRAME.components.dom ){
AFRAME.registerComponent('dom',{
requires: {
"requestAnimationFrameXR": "com/requestAnimationFrameXR.js"
},
init: function(){
Object.values(this.el.components)
.map( (c) => {
@ -127,12 +131,9 @@ if( !AFRAME.components.dom ){
return this
},
stubRequestAnimationFrame: function(){
// stub, because WebXR with overrule this (it will not call the callback as expected in immersive mode)
const requestAnimationFrame = window.requestAnimationFrame
window.requestAnimationFrame = (cb) => {
setTimeout( cb, 25 )
}
stubRequestAnimationFrame: async function(){
let s = await AFRAME.utils.require(this.requires)
this.el.setAttribute("requestAnimationFrameXR","")
}
})

View File

@ -1,4 +1,4 @@
if( !AFRAME.components['html-as-textre-in-xr'] ){
if( !AFRAME.components['html-as-texture-in-xr'] ){
AFRAME.registerComponent('html-as-texture-in-xr', {
schema: {
@ -12,6 +12,10 @@ if( !AFRAME.components['html-as-textre-in-xr'] ){
},
init: async function () {
let el = document.querySelector(this.data.domid)
if( ! el ){
return console.error("html-as-texture-in-xr: cannot get dom element "+this.data.dom.id)
}
let s = await AFRAME.utils.require(this.dependencies)
this.el.setAttribute("html",`html: ${this.data.domid}; cursor:#cursor; xrlayer: true`)
this.el.setAttribute("visible", AFRAME.utils.XD() == '3D' ? 'true' : 'false' )

View File

@ -37,10 +37,11 @@ if( typeof AFRAME != 'undefined '){
cols: { type: 'number',"default": 120 },
rows: { type: 'number',"default": 30 },
padding: { type: 'number',"default": 18 },
minimized: { type: 'boolean',"default":false},
maximized: { type: 'boolean',"default":true},
transparent: { type:'boolean', "default":false }, // need good gpu
xterm: { type: 'boolean', "default":true }, // use xterm.js (slower)
memory: { type: 'number', "default":32 } // VM memory (in MB)
memory: { type: 'number', "default":48 } // VM memory (in MB)
},
init: async function(){
@ -74,6 +75,8 @@ if( typeof AFRAME != 'undefined '){
javascript: "com/isoterminal/feat/javascript.js",
indexhtml: "com/isoterminal/feat/index.html.js",
indexjs: "com/isoterminal/feat/index.js.js",
autorestore: "com/isoterminal/feat/autorestore.js",
localforage: "https://cdn.rawgit.com/mozilla/localForage/master/dist/localforage.js"
},
dom: {
@ -90,16 +93,26 @@ if( typeof AFRAME != 'undefined '){
width:100%;
height:100%;
}
@font-face {
font-family: 'Cousine';
font-style: normal;
font-weight: 400;
src: url(./assets/Cousine.ttf) format('truetype');
}
.isoterminal *{
white-space: pre;
font-size: 14px;
font-family: Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
font-weight:900 !important;
letter-spacing: 0 !important;
line-height:16px;
display:inline;
overflow: hidden;
}
.isoterminal *,
.xterm-dom-renderer-owner-1 .xterm-rows {
font-size: 14px;
font-family: "Cousine",Liberation Mono,DejaVu Sans Mono,Courier New,monospace;
font-weight:500 !important;
letter-spacing: 0 !important;
text-shadow: 0px 0px 10px #F075;
}
.isoterminal style{ display:none }
@ -108,6 +121,10 @@ if( typeof AFRAME != 'undefined '){
overflow:hidden;
}
.XR .wb-body:has(> .isoterminal){
background: #000;
}
.isoterminal div{ display:block; }
.isoterminal span{ display: inline }
@ -163,7 +180,7 @@ if( typeof AFRAME != 'undefined '){
//instance.winbox.resize(720,380)
let size = this.data.xterm ? 'width: 1024px; height:600px'
: 'width: 720px; height:455px'
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}`)
instance.setAttribute("window", `title: xrsh.iso; uid: ${instance.uid}; attach: #overlay; dom: #${instance.dom.id}; ${size}; min: ${this.data.minimized}; max: ${this.data.maximized}`)
})
instance.addEventListener('window.oncreate', (e) => {
@ -179,8 +196,11 @@ if( typeof AFRAME != 'undefined '){
this.isoterminal.addEventListener('postReady', (e)=>{
// bugfix: send window dimensions to xterm (xterm.js does that from dom-sizechange to xterm via escape codes)
if( this.data.maximized ) instance.winbox.maximize()
else instance.winbox.resize()
let wb = instance.winbox
if( this.data.maximized ){
wb.restore()
wb.maximize()
}else wb.resize()
})
this.isoterminal.addEventListener('ready', (e)=>{
@ -208,11 +228,12 @@ if( typeof AFRAME != 'undefined '){
instance.addEventListener('window.onmaximize', resize )
const focus = (e) => {
if( this.isoterminal?.emulator?.serial_adapter?.focus ){
if( this.isoterminal?.emulator?.serial_adapter?.term ){
this.isoterminal.emulator.serial_adapter.term.focus()
}
}
//instance.addEventListener('obbcollisionstarted', focus )
instance.addEventListener('obbcollisionstarted', focus )
this.el.sceneEl.addEventListener('enter-vr', focus )
this.el.sceneEl.addEventListener('enter-ar', focus )

View File

@ -47,7 +47,7 @@ ISOTerminal.prototype.runISO = function(opts){
uart3:true, // /dev/ttyS3
wasm_path: "com/isoterminal/v86.wasm",
memory_size: opts.memory * 1024 * 1024,
vga_memory_size: 1024, //2 * 1024 * 1024,
vga_memory_size: 2 * 1024 * 1024,
screen_container: opts.dom,
//serial_container: opts.dom,
bios: {
@ -145,8 +145,8 @@ ISOTerminal.prototype.runISO = function(opts){
line += chr;
}
if( !ready && line.match(/^(\/ #|~%|\[.*\]>)/) ){
this.emit('postReady')
setTimeout( () => this.emit('ready'), 500 )
this.emit('postReady',e)
setTimeout( () => this.emit('ready',e), 500 )
ready = true
}
});

View File

@ -0,0 +1,55 @@
ISOTerminal.addEventListener('emulator-started', function(e){
this.autorestore(e)
})
ISOTerminal.prototype.convert = {
arrayBufferToBase64: function(buffer){
let binary = '';
const bytes = new Uint8Array(buffer);
const len = bytes.byteLength;
for (let i = 0; i < len; i++) binary += String.fromCharCode(bytes[i]);
return window.btoa(binary);
},
base64ToArrayBuffer: function(base64) {
const binaryString = window.atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return bytes.buffer;
}
}
ISOTerminal.prototype.autorestore = async function(e){
localforage.setDriver([
localforage.INDEXEDDB,
localforage.WEBSQL,
localforage.LOCALSTORAGE
]).then( () => {
localforage.getItem("state", (err,stateBase64) => {
if( !err && confirm('continue last session?') ){
this.noboot = true // see feat/boot.js
state = this.convert.base64ToArrayBuffer( stateBase64 )
this.emulator.restore_state(state)
this.emit('postReady',e)
setTimeout( () => {
this.emit('ready',e)
this.send("alert last session restored\n")
}, 500 )
}
})
this.save = async () => {
const state = await this.emulator.save_state()
console.log( String(this.convert.arrayBufferToBase64(state)).substr(0,5) )
localforage.setItem("state", this.convert.arrayBufferToBase64(state) )
}
})
}

View File

@ -1,8 +1,8 @@
ISOTerminal.addEventListener('ready', function(){
this.boot()
ISOTerminal.addEventListener('ready', function(e){
setTimeout( () => this.boot(), 50 ) // because of autorestore.js
})
ISOTerminal.prototype.boot = async function(){
ISOTerminal.prototype.boot = async function(e){
// set environment
let env = ['export BROWSER=1']
for ( let i in document.location ){
@ -10,10 +10,13 @@ ISOTerminal.prototype.boot = async function(){
env.push( 'export '+String(i).toUpperCase()+'="'+document.location[i]+'"')
}
await this.emulator.create_file("profile.browser", this.toUint8Array( env.join('\n') ) )
if( this.serial_input == 0 ){
if( !this.noboot ){
let boot = "source /etc/profile\n"
this.send(boot+"\n")
}
}
if( this.emulator.serial_adapter ) this.emulator.serial_adapter.term.focus()
else{

View File

@ -16,9 +16,7 @@ ISOTerminal.prototype.xtermInit = function(){
window.Terminal = function(opts){
const term = new window._Terminal({ ...opts,
cursorBlink:true,
onSelectionChange: function(e){
debugger
},
onSelectionChange: function(e){ console.log("selectchange") },
letterSpacing: 0
})
@ -38,7 +36,7 @@ ISOTerminal.prototype.xtermInit = function(){
const resize = (w,h) => {
setTimeout( () => {
isoterm.xtermAutoResize(isoterm.emulator.serial_adapter.term, isoterm.instance,-2)
isoterm.xtermAutoResize(isoterm.emulator.serial_adapter.term, isoterm.instance,-3)
},800) // wait for resize anim
}
isoterm.instance.addEventListener('window.onresize', resize )

View File

@ -0,0 +1,50 @@
/*
* ## requestAnimationFrameXR
*
* reroutes requestAnimationFrame-calls to xrSession.requestAnimationFrame
* reason: in immersive mode this function behaves differently
* (causing HTML apps like xterm.js not getting updated due to relying
* on window.requestAnimationFrame)
*
* ```html
* <a-entity requestAnimationFrameXR dom/>
* ```
*/
if( !AFRAME.systems.requestAnimationFrameXR ){
AFRAME.registerSystem('requestAnimationFrameXR',{
init: function init(){
if( document.location.hostname.match(/localhost/) ) return // allow webxr polyfill during development (they hang in XR)
AFRAME.systems.requestAnimationFrameXR.q = []
this.sceneEl.addEventListener('enter-vr', this.enable )
this.sceneEl.addEventListener('enter-ar', this.enable )
this.sceneEl.addEventListener('exit-vr', this.disable )
this.sceneEl.addEventListener('exit-ar', this.disable )
},
enable: function enable(){
this.requestAnimationFrame = window.requestAnimationFrame
// NOTE: we don't call xrSession.requestAnimationFrame directly like this:
//
// window.requestAnimationFrame = AFRAME.utils.throttleTick( (cb) => this.sceneEl.xrSession.requestAnimationFrame(cb), 50 )
//
// as that breaks webxr polyfill (for in-browser testing)
// instead we defer calls to tick() (which is called both in XR and non-XR)
//
window.requestAnimationFrame = (cb) => AFRAME.systems.requestAnimationFrameXR.q.push(cb)
const q = AFRAME.systems.requestAnimationFrameXR.q
this.tick = AFRAME.utils.throttleTick( () => {
while( q.length != 0 ) (q.pop())()
},50)
},
disable: function disable(){
delete this.tick
window.requestAnimationFrame = this.requestAnimationFrame
}
})
}

View File

@ -7,6 +7,7 @@ AFRAME.registerComponent('window', {
attach: {type:'selector'},
dom: {type:'selector'},
max: {type:'boolean',"default":false},
min: {type:'boolean',"default":false},
x: {type:'string',"default":"center"},
y: {type:'string',"default":"center"}
},
@ -35,6 +36,7 @@ AFRAME.registerComponent('window', {
root: this.data.attach || document.body,
mount: this.data.dom,
max: this.data.max,
min: this.data.min,
onresize: () => this.el.emit('window.onresize',{}),
onmaximize: () => this.el.emit('window.onmaximize',{}),
oncreate: (e) => {