26. Backend UI JavaScript nel browser
MALDA supporta anche un modello UI ospitato nel browser in cui il sorgente MALDA viene transpile in JavaScript e gira con il runtime JS di MALDA. È distinto dai flussi server-side @PAGE, @AIPAGE e dai componenti server.
26.1 Mappa del capitolo
- 26.2 Cosa offre il backend JavaScript
- 26.3 Comandi di compile e modalità di output
- 26.4 Ordine obbligatorio di caricamento degli script
- 26.5 Helper DOM in API mode
- 26.6 Sum type e pattern matching in JS mode
- 26.7 Actor in JS mode
- 26.8 Sintassi del template mode
- 26.9 API canvas/game
- 26.10 API scene three.js
- 26.10.1 Kernel shader (MALDA verso GLSL)
- 26.11 Errori comuni e indicazioni pratiche
26.2 Cosa offre questa modalità
- API mode (
.malda): scrivi MALDA che manipola il DOM con gli helperdom.*. - Template mode (
.malda.html): scrivi template HTML-first con blocchi MALDA inline. - Copertura del linguaggio: il backend JavaScript supporta i sum type e il pattern matching
matchcompleto. - Supporto actor (locali): JS mode supporta actor locali con semantica di mailbox compatibile con il comportamento actor MALDA.
- Supporto scene 3D: JavaScript mode include un wrapper curato
three.*per scene 3D ospitate nel browser, più kernel@shader()compilati in GLSL (vedi 26.10). - Aspettativa di runtime: gli script generati richiedono
malda-js-runtime.jsprima che l'output MALDA venga eseguito. - Debug nell'IDE Desktop: F5 su programmi
dom.*/game.*/three.*transpila in JavaScript, apre Web Preview e mappa i breakpoint dell'editor tramite source map. I file full-stack (@client()più@server()o una route) eseguono il debug dell'interprete host e di Web Preview insieme (vedi 2.6.6).
26.2.1 Decoratori target nell'output JS
Quando compili verso JavaScript, i decoratori target servono a tenere le preoccupazioni browser e server in un unico file sorgente:
- Le dichiarazioni
@client()e@javascript()sono incluse nell'output JavaScript. - Le dichiarazioni
@server()e@csharp()sono escluse dall'output JavaScript. - Le dichiarazioni
@shared()sono incluse sia nell'output C# sia in quello JavaScript.
I decoratori di route restano orientati al server. Una dichiarazione come @GET(...) è trattata come codice server e non viene emessa nell'output JavaScript.
26.3 Comandi di compile
# API mode (.malda source)
malda compile Examples/Web/js/hello_dom.malda --mode js -o Examples/Web/js/hello_dom.js
# Alias form (equivalent)
malda compile Examples/Web/js/counter.malda --target js -o Examples/Web/js/counter.js
# PWA output directory
malda compile Examples/Web/js/hello_dom.malda --target pwa -o dist/hello-dom
# Template mode (.malda.html source)
malda compile Examples/Web/js/hello_template.malda.html --mode js -o Examples/Web/js/hello_template.generated.js
--target js è un alias di --mode js. --target pwa è un alias di --mode pwa e produce una directory che contiene l'app shell, il runtime e il JavaScript transpile.
Il ciclo interno per i giochi canvas è malda play. Compila --mode js, scrive una pagina host e serve un URL locale così non devi scrivere HTML. Il packaging per itch.io resta --mode pwa.
malda new game my-game
cd my-game
malda play app.malda
Canvas più API punteggi è malda new game --fullstack. Emette @client() / @GET / @POST e schema Score in un solo file. Compila con --mode fullstack; malda play rifiuta quelle sorgenti.
malda new game my-scores --fullstack
cd my-scores
malda compile app.malda --mode fullstack -o dist
26.4 Ordine obbligatorio di caricamento degli script
La pagina host deve includere un contenitore come #app, poi caricare gli script in quest'ordine. Stesso file: Examples/Web/js/host.html.
- Qualsiasi dipendenza browser richiesta per il namespace scelto, per esempio
three.jsperthree.* malda-js-runtime.js- Lo script MALDA compilato
- La chiamata di bootstrap, come
MaldaApp.main()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>MALDA JS host</title>
</head>
<body>
<div id="app"></div>
<script src="../wwwroot/malda-js-runtime.js"></script>
<script src="./counter.js"></script>
<script>
if (window.MaldaApp && typeof window.MaldaApp.main === "function") {
window.MaldaApp.main();
}
</script>
</body>
</html>
println è un helper di console del backend JavaScript. L'interprete non ha println (usa io.print / print — vedi 12. Input/Output).
26.5 Helper DOM in API mode
Il runtime JavaScript espone helper browser sotto dom per la manipolazione diretta del DOM. Stesso programma: Examples/Web/js/counter.malda (compila con il comando in 26.3, poi apri la pagina host di 26.4).
dom.query(selector, root?)dom.create(tagName)dom.append(parent, child)dom.clear(target)dom.setText(target, text)dom.html(target, markup)dom.on(target, eventName, handler, options?)
var root = dom.query("#app");
if (root == null) {
println("No #app container found.");
} else {
dom.clear(root);
var count = 0;
var title = dom.create("h1");
dom.setText(title, "Counter example");
dom.append(root, title);
var valueLabel = dom.create("p");
dom.append(root, valueLabel);
function renderCount() {
dom.setText(valueLabel, "Count: " + string(count));
}
var button = dom.create("button");
dom.setText(button, "Increment");
dom.append(root, button);
function onIncrementClick() {
count = count + 1;
renderCount();
}
dom.on(button, "click", onIncrementClick);
renderCount();
}
26.6 Sum type e pattern matching in JS mode
Puoi usare lo stesso modello di sum type e pattern matching in JavaScript backend mode come in modalità interpretata.
type Result = Ok(value) | Err(message);
function divide(a, b) {
if (b == 0) return Err("divide by zero");
return Ok(a / b);
}
var outcome = divide(10, 2);
var text = match outcome {
case Ok(v): "ok: " + v;
case Err(msg): "error: " + msg;
};
println(text);
Tutte le famiglie di pattern sono supportate in JS mode: letterali, identificatori, wildcard, payload delle variant, pattern di array con ...rest e pattern di oggetto nidificati.
26.7 Actor in JS mode
Il backend JavaScript include il supporto al runtime actor locale tramite mlRuntime.actors. Offre concorrenza actor lato browser o Node-local con semantica di mailbox allineata al comportamento actor MALDA.
- Funzionalità supportate:
actor,spawn,sendin stile call, send con callback e timeout,reply(),receive(),selfe routing di stop. - Il processing FIFO della mailbox per actor è preservato.
send target.stop();passa dal comportamento di stop consapevole degli actor.
Confine: è solo supporto al runtime actor locale. La comunicazione trasparente diretta tra actor JS nel browser e actor lato server non è abilitata di default.
26.8 Sintassi del template mode
Il template mode usa file .malda.html. Usa {{ ... }} per l'interpolazione e {% ... %} per le istruzioni.
<div class="hello-template">
<h1>Hello from template mode</h1>
<p>User: {{ "Andrea" }}</p>
{% var count = 2; %}
<p>Count value: {{ count }}</p>
</div>
Vedi Examples/Web/js/host.html per una pagina statica minima che mostra il caricamento del runtime e il bootstrap dello script compilato.
26.9 API Game Canvas
Il backend JavaScript include un'API canvas/game nel browser sotto game.* per loop interattivi 2D semplici.
- Canvas e ciclo di vita:
game.createCanvas,game.setBackground,game.start(updateFn, renderFn?),game.startFixed(updateFn, renderFn?, tickMs?),game.stop(),game.save(key, value),game.load(key),game.removeSave(key) - Disegno:
game.clear(),game.fillRect(...),game.fillCircle(...),game.drawText(...),game.drawLine(...),game.strokeRect(...),game.setAlpha(a) - Immagini e camera:
game.loadImage(url),game.imageIsReady(handle),game.drawImage(...),game.drawImageRect(...),game.setCamera(x, y),game.getCameraX(),game.getCameraY() - Buffer pixel / blit:
game.createPixelBuffer(width?, height?),game.setPixel(x, y, r, g, b, a?),game.blitPixels(pixels?, destX?, destY?) - Collisione:
game.overlapRect(x1, y1, w1, h1, x2, y2, w2, h2),game.overlapCircle(x1, y1, r1, x2, y2, r2),game.pointInRect(px, py, x, y, w, h),game.pointInCircle(px, py, x, y, r) - Input:
game.isKeyDown(key),game.wasKeyPressed(key),game.wasKeyReleased(key),game.getMouseX(),game.getMouseY(),game.isMouseDown(button?),game.getTouches(),game.isGamepadConnected(index?),game.getGamepadAxis(index, axis),game.isGamepadButtonDown(index, button),game.wasGamepadButtonPressed(index, button) - Audio:
game.audioInit(),game.audioIsReady(),game.audioSetMasterVolume(v),game.audioPlayTone(...),game.audioPlayNoise(...),game.audioPlayPattern(pattern),game.audioStopPattern(),game.audioPlaySample(url, volume?, options?),game.audioStopSample(url?),game.audioLoadTrack(...),game.audioPlayTrack(),game.audioStopTrack(),game.audioSetTrackOptions(options),game.audioTrackIsReady(),game.audioGetTrackInfo(),game.audioStopAll()
Indicazioni su immagini e camera: game.loadImage restituisce subito un handle; la decode è asincrona. I draw sono no-op finché game.imageIsReady è true (i file mancanti restano unready, senza throw). game.setCamera sottrae i draw successivi nel mondo (fillRect, drawImage, linee, testo). Non sposta setPixel / blitPixels. Le coordinate del mouse restano in pixel del canvas. Vedi Examples/Games/game_sprite_smoke.malda.
Indicazioni sull'input: I fronti di tasti e gamepad sono snapshot all'inizio di update e sono false in render. Leggi wasKeyPressed / wasKeyReleased / wasGamepadButtonPressed solo in update (stessi nomi di isKeyDown). getTouches() restituisce { id, x, y } in pixel del canvas (vuoto se nessuno). Il primo tocco attivo continua ad aliasare il pulsante mouse 0. API Gamepad assente o pad scollegati: disconnected / asse 0. game.stop() azzera tasti e fronti dei pulsanti. Vedi Examples/Games/game_input_smoke.malda.
Indicazioni sulla collisione: Gli helper di overlap sono inclusivi (i bordi che si toccano contano) e restituiscono false se larghezza, altezza o raggio sono ≤ 0. Sono funzioni pure: niente canvas, e non sottraggono setCamera. Non sono AABB swept né fisica. Vedi Examples/Games/game_collision_smoke.malda.
Indicazioni sul loop: updateFn riceve dtMs. Scala il movimento per dtMs così l'animazione resta indipendente dal frame-rate. game.startFixed(updateFn, renderFn?, tickMs?) usa di default tickMs = 1000 / 60, accumula il tempo wall e chiama update(tickMs) zero o più volte per frame (max 5), poi render una volta. Non può girare insieme a game.start. Vedi Examples/Games/game_fixed_save_smoke.malda.
Indicazioni sul save: game.save / game.load / game.removeSave salvano JSON in localStorage scoped all'origine sotto il prefisso malda.game. (non file). Storage assente, quota o JSON corrotto: load restituisce null e save è no-op (senza throw).
Indicazioni sull'audio: chiama game.audioInit() dopo un gesto dell'utente per soddisfare le policy di autoplay del browser. game.audioPlaySample(url, volume?, options?) decodifica un WAV/OGG una volta per URL e lo mette in cache (volume default 1, clamp [0, 1]; { loop: true } ripete). I one-shot possono sovrapporsi. game.audioStopSample(url?) ferma solo i sample — non ferma la track v1, il pattern o i toni. game.stop() non ferma implicitamente l'audio. I sample condividono il cap di 32 nodi con i toni. Vedi Examples/Games/game_audio_sample_smoke.malda.
Per un avvio rapido completo e le best practice, vedi docs/javascript-backend.md. Per un sample completo, vedi Examples/Games/game_bounce.malda. Per blit di un atlas PNG e una camera che scorre, vedi Examples/Games/game_sprite_smoke.malda. Per fronti di tasti, tocchi e gamepad, vedi Examples/Games/game_input_smoke.malda. Per overlap AABB e cerchi, vedi Examples/Games/game_collision_smoke.malda. Per one-shot WAV sovrapposti accanto a un pattern in loop, vedi Examples/Games/game_audio_sample_smoke.malda. Per un timestep fisso e un high score che sopravvive al reload, vedi Examples/Games/game_fixed_save_smoke.malda. Per un side-scroller breve che usa il kit G1–G5 insieme (tile da atlas, camera, AABB, fronti di tasti, sample SFX, startFixed), vedi Examples/Games/malda_platform.malda. Per un client canvas più schema / validate sui punteggi, usa malda new game --fullstack. Per una cave in stile Boulder Dash (terra, massi con gravità, diamanti, lucciole), vedi Examples/Games/maldadash.malda. Per un ray tracer CPU che scrive i pixel in un buffer ImageData e fa blit una volta per frame, vedi Examples/Games/ray_tracer.malda.
26.10 API scene three.js
Il backend JavaScript include un'API scene 3D curata sotto three.*. Usala quando vuoi una scena nel browser in MALDA senza scrivere chiamate THREE.* grezze. Il namespace è solo JavaScript mode; l'interprete e il transpile C# non eseguono questi helper. Le chiamate mappano su mlRuntime.three.* nello script generato.
Costruisci una scena in questo ordine: renderer, scene, camera, poi mesh e luci, poi three.start. three.createRenderer(width, height, mountSelector?) crea il canvas WebGL e lo appende all'elemento host (default "#app"). Chiamalo una volta, prima di three.render o three.start. Se ti serve una nuova dimensione o un secondo canvas, chiama prima three.stop() — non creare un altro renderer mentre il loop è in esecuzione.
- Renderer e ciclo di vita:
three.createRenderer,three.setClearColor,three.setRendererSize,three.start(updateFn, renderFn?),three.stop() - Scene graph:
three.createScene(),three.createPerspectiveCamera(fovDeg, aspect, near, far),three.createOrthographicCamera(left, right, top, bottom, near, far),three.setCameraAspect(camera, aspect),three.createGroup(),three.createMesh(geometry, material),three.add(parent, child),three.loadGLTF(url),three.modelIsReady(handle) - Primitive e lighting:
three.createBoxGeometry(width, height, depth),three.createPlaneGeometry(width, height),three.createSphereGeometry(radius, widthSegments?, heightSegments?),three.createTexture(url),three.createStandardMaterial(options),three.createShaderMaterial(options),three.setUniform(material, name, value),three.createDirectionalLight(color, intensity),three.createAmbientLight(color?, intensity?) - Transform e input:
three.setPosition(object, x, y, z),three.setRotation(object, x, y, z),three.setScale(object, x, y, z),three.lookAt(object, x, y, z),three.render(renderer, scene, camera),three.isKeyDown(key),three.getMouseX(),three.getMouseY(),three.isMouseDown(button?)
Posizioni, scale e near/far della camera sono in unità mondo. Le rotazioni sono in radianti (un pavimento sul piano XZ è circa -1.5708 su X). Le opzioni di standard-material che il wrapper capisce includono "color" (#rrggbb), "roughness", "metalness" e "map" (un handle di three.createTexture). createTexture restituisce subito; la decode è asincrona e i file mancanti restano unready (senza throw). Il materiale lascia map unset finché l'handle è ready. three.loadGLTF(url) restituisce un group che puoi add subito; i children compaiono quando three.modelIsReady(handle) è true (JSON .gltf o .glb; i fallimenti restano unready). Il runtime possiede il loader — le pagine host non aggiungono un quarto <script>. three.lookAt(object, x, y, z) richiede lookAt sull'oggetto three.js. I group parentano le mesh così puoi muovere un insieme con un solo setPosition / setRotation. I tasti di input usano gli stessi nomi dell'API canvas ("arrowleft", "arrowright", …).
Una prima scena con un cubo che gira, una key light e un loop su dtMs:
var width = 800;
var height = 500;
var aspect = width / height;
var renderer = three.createRenderer(width, height, "#app");
three.setClearColor(renderer, "#101722");
three.setRendererSize(renderer, width, height);
var scene = three.createScene();
var camera = three.createPerspectiveCamera(70, aspect, 0.1, 100.0);
three.setCameraAspect(camera, aspect);
three.setPosition(camera, 0, 0, 5);
var geometry = three.createBoxGeometry(1, 1, 1);
var material = three.createStandardMaterial({ "color": "#44aaff" });
var cube = three.createMesh(geometry, material);
three.add(scene, cube);
var light = three.createDirectionalLight("#ffffff", 1.1);
three.setPosition(light, 2, 3, 4);
three.add(scene, light);
var angle = 0.0;
function update(dtMs) {
angle = angle + ((dtMs * 1.2) / 1000);
three.setRotation(cube, angle, angle * 0.8, 0.0);
}
function render() {
three.render(renderer, scene, camera);
}
three.start(update, render);
Indicazioni sul loop: updateFn riceve dtMs, come game.start. Scala rotazione e movimento per dtMs / 1000 così l'animazione resta indipendente dal frame-rate. Limita i valori enormi di dtMs dopo che un tab è stato in background se un singolo step saltrebbe troppo. Metti il disegno in renderFn (oppure omettilo e fai render alla fine di update).
Ordine di caricamento: la pagina host deve caricare un bundle browser compatibile che definisce globalThis.THREE prima di malda-js-runtime.js e prima dello script MALDA compilato, poi chiamare MaldaApp.main(). Il repository include Examples/Web/wwwroot/vendor/three.min.js a questo scopo.
Examples/Web/wwwroot/vendor/three.min.jsmalda-js-runtime.js- compiled MALDA script
MaldaApp.main()
Indicazioni sul resize: se la pagina host cambia il viewport di render dopo il setup, aggiorna sia il renderer sia la camera: three.setRendererSize(renderer, width, height) e three.setCameraAspect(camera, width / height).
Scope MVP: orbit control e interop JS grezzo generico restano fuori da questa API. Texture e caricamento glTF ci sono: vedi Examples/Games/three_textured.malda. Le mesh illuminate usano three.createStandardMaterial. Il GLSL custom usa three.createShaderMaterial e three.setUniform — vedi 26.10.1.
Compila il demo del cubo con:
malda compile Examples/Games/three_cube.malda --mode js -o Examples/Games/three_cube.js
Vedi Examples/Games/three_cube.malda e Examples/Games/three_runtime_smoke_test.html per la pagina host end-to-end. Per una map PNG, un cubo glTF e lookAt, vedi Examples/Games/three_textured.malda. Un listing più breve è in 33.15 Backend JavaScript (API scena three.js). Note operative: docs/javascript-backend.md.
26.10.1 Kernel shader (MALDA verso GLSL)
Quando game.setPixel sulla CPU è troppo lento, sposta il lavoro per-pixel sulla GPU. Questo è un sottoinsieme a compile-time JavaScript, non un quarto backend di esecuzione. Il MALDA host continua a possedere il loop three.* e gli aggiornamenti delle uniform. Il kernel possiede i raggi.
Marca le funzioni GPU con @shader() e raccoglile in una stringa GLSL con glsl.compile({ ... }). Quelle funzioni non vengono emesse come JavaScript o C#. Non chiamarle dal MALDA host. L'interprete e il transpile C# le saltano.
@shader()
function vertexMain() {
vUv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
var vert = glsl.compile({
varyings: ["vec2 vUv"],
functions: ["vertexMain"],
main: "vertexMain"
});
var material = three.createShaderMaterial({
"vertexShader": vert,
"fragmentShader": frag,
"uniforms": { "uTime": 0 }
});
three.setUniform(material, "uTime", t);
glsl.compile accetta un solo object literal. Chiavi:
varyings/uniforms/consts— array di stringhe di dichiarazione GLSL, per esempio"vec3 uCamPos"o"float EPSILON = 0.0015"functions— nomi delle funzioni@shader()in questo file da emettere, in ordinemain— quale di quelle funzioni diventa ilvoid main()GLSL (usalo quando il nome MALDA èvertexMain/fragmentMain, perché un file non può dichiarare due funzioni MALDA chiamatemain)header— prefisso GLSL grezzo opzionale
Passa le stringhe risultanti a three.createShaderMaterial come "vertexShader" / "fragmentShader". I valori uniform dal MALDA host sono wrappati come { value }. Gli array di lunghezza 2/3/4 diventano Vector2 / Vector3 / Vector4. Le stringhe #rrggbb diventano Color. Flag opzionali: "depthWrite": false, "depthTest": false, "transparent": true.
Per un pass a schermo intero usa three.createOrthographicCamera(-1, 1, 1, -1, 0, 1) e three.createPlaneGeometry(2, 2).
Tipi nei kernel
vec2, vec3, vec4 e gli altri nomi vettore/matrice GLSL non sono tipi MALDA. Sono nomi GLSL memorizzati come type hint e copiati nello shader. Fuori da @shader(), var p: vec3 = ... è un Error di hint sconosciuto. I nomi che esistono già in entrambi i linguaggi — float, int, bool, void — restano hint MALDA ordinari.
Parametri e locali dello shader richiedono un type hint GLSL. I parametri che in GLSL si scriverebbero con out usano un hint di due parole: tHit: out float. Le uniform host restano array MALDA come [camX, camY, camZ], non valori vec3.
Sottoinsieme del linguaggio
Ammessi: if / else, while, for, return, aritmetica e confronti, and / or / not (emessi come && / || / !), swizzle di membro come position.xy, costruttori e builtin GLSL (vec3, dot, normalize, mix, clamp, …), e math.sqrt → GLSL sqrt (stesso per gli altri nomi math.* mappati).
Non ammessi nei kernel: actor, prompt, match, stringhe, foreach, await, classi e I/O MALDA. Un costrutto che l'emettitore GLSL non conosce fa fallire il compile JS con un errore GLSL transpile alla riga MALDA.
IDE
Poiché i kernel sono dichiarazioni function MALDA ordinarie, nel Desktop IDE e nell'LSP rename, trova riferimenti, highlight e outline si applicano agli identificatori nel kernel — a differenza del GLSL grezzo in una stringa """, che è un solo token. Rename continua a matchare il lessema in tutto il file (come nel resto di MALDA). Non riscrive le stringhe in glsl.compile (functions: ["hitSphere"], uniforms: ["vec3 uCamPos"], main: "fragmentMain"). Dopo aver rinominato un identificatore di kernel o di uniform, aggiorna quelle stringhe. I builtin GLSL (dot, gl_FragColor, nome di tipo vec3) non hanno una dichiarazione MALDA, quindi vai alla definizione non ha dove andare.
GLSL grezzo
Le stringhe GLSL tra triple quotes restano valide quando ti serve sintassi che il sottoinsieme non emette. Usa """, non $""", così le graffe restano letterali. Preferisci @shader() quando il kernel sta nel sottoinsieme, così rename e outline possono vederlo.
Compila il tracer GPU in tempo reale di sfere con:
malda compile Examples/Games/three_shader_raytracer.malda --mode js -o Examples/Games/three_shader_raytracer.js
malda compile Examples/Games/three_shader_path_tunnel.malda --mode js -o Examples/Games/three_shader_path_tunnel.js
Carica three.min.js per primo, come negli altri demo three.*. Un listing breve è in 33.15.1 Materiali shader. Note operative: docs/javascript-backend.md. Il sample path-tunnel è CC-BY-NC-SA-4.0 (Frostbyte).
26.11 Errori comuni e indicazioni
- Non modificare direttamente il JavaScript generato quando esiste un sorgente
.maldacorrispondente. Cambia il file MALDA e ricompila. - Carica
malda-js-runtime.jsprima dello script compilato, altrimenti l'app generata fallisce a runtime. - Carica
Examples/Web/wwwroot/vendor/three.min.jsprima dimalda-js-runtime.jsquando usi il namespacethree.*. three.*è JavaScript mode con un host browser. Chiamathree.createRendererprima dithree.startothree.render. Non creare un secondo renderer mentre il loop è in esecuzione; primathree.stop().- Le rotazioni sono in radianti. Dopo un cambio di viewport, chiama sia
three.setRendererSizesiathree.setCameraAspect. - Tieni le preoccupazioni solo-browser in JS mode e quelle solo-server in
@PAGE/@AIPAGEo nei componenti server. - Non combinare
@client()con decoratori di route come@GETo@PAGE; è una combinazione di target non valida. - Usa
@shared()solo per logica sicura in entrambi i runtime; tieni filesystem/database/API server e API solo-browser fuori dalle dichiarazioni shared. - Crea il canvas prima di disegnare o di avviare un game loop.
- Usa
malda new gameemalda play app.maldainvece di scrivere un file HTML host.malda playserve.malda-play/, non l'albero sorgente. Il packaging resta--mode pwa. Canvas più punteggi èmalda new game --fullstackpoimalda compile --mode fullstack;malda playrifiuta quelle sorgenti. - F5 dell'interprete non può fare debug di programmi
dom.*/game.*/three.*. Usa F5 dell'IDE Desktop (Web Preview) oppure i DevTools del browser sul.js+.mapcompilato. - Usa API mode quando vuoi il controllo DOM completo e template mode quando la pagina è soprattutto HTML con poca logica MALDA.
- Le funzioni
@shader()sono kernel GLSL, non chiamabili dall'host. Compilale conglsl.compilee passa la stringa athree.createShaderMaterial. - Rename Symbol aggiorna gli identificatori MALDA, non i nomi stringa dentro
glsl.compile({ ... }). Tieni quelle liste allineate. - Non usare
vec3/vec2come tipi MALDA fuori da@shader(). Le uniform host sono array come[x, y, z].
Vedi anche
- 23. Panoramica Web UI - Quale modello UI scegliere
- 24. Componenti server Web UI - UI renderizzata sul server con fragment, aggiornamenti live e
ui.* - HttpServer e generazione UI HTML - Hosting delle route e generazione HTML AI
- 33.15 Backend JavaScript (three.js) - Listing del cubo e schizzo materiale shader
- Sviluppo full-stack con MALDA - Scegliere tra UI renderizzata sul server, API-first e UI ospitata nel browser