import { is, Dataspace, Embedded, Reader, Ref, Schemas, Sturdy, Turn } from "@syndicate-lang/core"; import * as html from "@syndicate-lang/html"; import * as timer from "@syndicate-lang/timer"; import * as wsRelay from "@syndicate-lang/ws-relay"; import * as wakeDetector from './wake-detector.js'; import * as Shapes from './gen/shapes.js'; import * as SceneProtocol from './gen/scene.js'; import { md5 } from './md5.js'; import { AbstractMesh, Engine, FreeCamera, Mesh, Scene, TargetCamera, WebXRCamera, } from '@babylonjs/core/Legacy/legacy'; import { activeFloorMeshes, activeTouchableMeshes, ShapeTree, builder as B } from './shapes.js'; import { RunningEngine, CreatedScene } from './engine.js'; import { uuid } from './uuid.js'; assertion type SceneHandle(ds: Embedded); function interpretScene(myId: string, scene: Scene, sceneDs: Ref) { at sceneDs { during Shapes.Sprite({ "name": $name: string }) => spawn named `sprite:${name}` { if (name === myId) { console.log('ignoring sprite', name); } else { console.log('+shape', name); on stop console.log('-shape', name); spriteMain(name, scene, sceneDs); } } } } function spriteMain(spriteName: string, scene: Scene, sceneDs: Ref) { at sceneDs { let currentShape = ShapeTree.empty(spriteName, scene); on stop currentShape.remove(); during Shapes.Sprite({ "name": spriteName, "shape": $shape: Shapes.Shape }) => { currentShape = currentShape.reconcile(spriteName, spriteName, shape); } } } function wsurl(): string { const scheme = (document.location.protocol.toLowerCase() === 'https:') ? 'wss' : 'ws'; return `${scheme}://${document.location.host}/ws`; } function bootApp(ds: Ref, runningEngine: RunningEngine) { spawn named 'app' { at ds { const id = uuid(); const url = wsurl(); const serverCap = Sturdy.asSturdyRef(new Reader( '').next()); const relayAddr = wsRelay.RelayAddress(Schemas.transportAddress.WebSocket(url)); field email: string = localStorage.getItem('userEmail') ?? id; const outerFacet = Turn.activeFacet; const emailInput = document.getElementById('emailInput') as HTMLInputElement; emailInput.value = email.value; emailInput.addEventListener('keyup', () => outerFacet.turn(() => { email.value = emailInput.value; localStorage.setItem('userEmail', emailInput.value); })); during wsRelay.Resolved({ "addr": relayAddr, "sturdyref": serverCap, "resolved": $remoteDs_e: Embedded, }) => { const remoteDs = remoteDs_e.embeddedValue; on message wakeDetector.WakeEvent() => { send message wsRelay.ForceRelayDisconnect(relayAddr); } at remoteDs { during SceneHandle($sceneDs_e: Embedded) => { const thisFacet = Turn.activeFacet; const sceneDs = sceneDs_e.embeddedValue; interpretScene(id, runningEngine.createdScene.scene, sceneDs); const camera = runningEngine.camera; camera.onCollide = (other: AbstractMesh) => { if (other.metadata?.touchable) { thisFacet.turn(() => { at sceneDs { send message SceneProtocol.Touch({ subject: id, object: other.metadata?.spriteName, }); } }); } }; field position: Shapes.Vector3 = Shapes.Vector3({ x:0, y:0, z:0 }); field rotation: Shapes.Vector3 = Shapes.Vector3({ x:0, y:0, z:0 }); at ds { const refreshPeriod = Math.floor(1000 / 10); on message timer.PeriodicTick(refreshPeriod) => { if (camera instanceof TargetCamera) { const newPosition = Shapes.Vector3(camera.position); const newRotation = Shapes.Vector3(camera instanceof WebXRCamera ? camera.rotationQuaternion.toEulerAngles() : camera.rotation); if (!is(Shapes.fromVector3(position.value), Shapes.fromVector3(newPosition))) { position.value = newPosition; } if (!is(Shapes.fromVector3(rotation.value), Shapes.fromVector3(newRotation))) { rotation.value = newRotation; } } } } at sceneDs { on message SceneProtocol.Touch({ "subject": id, "object": $o }) => { console.log('touch!', o); react { on stop console.log('portal check ending', o); stop on asserted SceneProtocol.Portal({ "name": o, "destination": $dest: SceneProtocol.PortalDestination }) => react { console.log('portal!', dest); switch (dest._variant) { case "local": at dest.value { assert 909909909; } break; default: break; } } const checkFacet = Turn.activeFacet; Turn.active.sync(sceneDs).then(() => checkFacet.turn(() => { console.log('synced'); stop {} })); } } assert Shapes.Sprite({ name: id, shape: B.nonphysical( B.move(position.value, B.many([ B.move({ x: 0, y: -0.9, z: 0 }, B.rotate({ x: 0, y: rotation.value.y, z: 0 }, B.scale({ x: 0.4, y: 1.4, z: 0.1 }, B.box()))), B.rotate(rotation.value, B.scale({ x: 0.15, y: 0.23, z: 0.18 }, B.many([ B.box(), B.move({ x: 0, y: 0, z: 0.501 }, B.texture( Shapes.TextureSpec.uvAlpha({ path: `https://www.gravatar.com/avatar/${md5(new TextEncoder().encode(email.value.trim()))}?s=256&d=wavatar`, scale: Shapes.Vector2({ x:1, y:1 }), offset: Shapes.Vector2({ x:0, y:0 }), alpha: 1 }), B.rotate({ x: 0, y: Math.PI, z: 0 }, B.plane()))), ]))), ]))), }); } } } } } } } window.addEventListener('load', async () => { const runningEngine = await RunningEngine.start( async (engine: Engine): Promise => ({ scene: new Scene(engine), floorMeshes: () => activeFloorMeshes, touchableMeshes: () => activeTouchableMeshes, })); Dataspace.boot(ds => { html.boot(ds); timer.boot(ds); wsRelay.boot(ds, false); wakeDetector.boot(ds); bootApp(ds, runningEngine); }); });