Saltar al contenido

Bucle de Render y Servidor HTTP

En esta página

Bucle de renderización con wgpu

El demo 07-render-loop muestra un bucle de eventos winit que renderiza 1000 cubos en una cuadrícula 10×10×10 con instancing GPU. El módulo separado config.zolo expone get_clear_color() — basta con editarlo y guardarlo mientras la ventana está abierta para ver el color de fondo cambiar en el siguiente frame, sin reinicializar el pipeline.

cd examples/features/21-hot-reload/07-render-loop
zolo dev main.zolo

Cambia los valores r/g/b aquí para modificar el color de fondo en tiempo real.

config.zolo
Playground
pub fn get_clear_color() {
  return #{r: 0.1, g: 0.02, b: 0.05, a: 1.0}
}

Requiere la CLI o el host de Zolo; ábrelo en el playground o ejecútalo localmente.

Bucle de eventos winit + wgpu: init_gpu(), render() y on_event() con match.

main.zolo
Playground
/// 1,000 Rotating Cubes — winit + wgpu

///

/// Demonstrates instanced rendering of 1K cubes in a 10×10×10 grid,

/// each with unique rotation. Uses GPU instancing via instance_index.


use std::os

use plugin winit::*
use plugin wgpu::*
use plugin wgpu
use config::{get_clear_color}

let win = Window.new(#{title: "Zolo — 1K Cubes", width: 800, height: 600})
let event_loop = EventLoop.new()

// GPU state — initialized on first redraw (after window is realized)

var gpu_ready = false
var adapter_h = nil
var device_h = nil
var queue_h = nil
var surface_h = nil
var pipeline_h = nil
var bind_group_h = nil
var uniform_buf_h = nil
var depth_view_h = nil
var frame_count = 0
var win_w = 800
var win_h = 600

// FPS counter

var fps_last_time = os.clock()
var fps_frames = 0
var fps_current = 0

// Frame pacing (144 FPS target)

const TARGET_FRAME_TIME = 1.0 / 144.0
var frame_start = os.clock()

const shader_src = /*wgsl*/ r"

  struct Uniforms { time: f32, aspect: f32 }
  @group(0) @binding(0) var<uniform> u: Uniforms;

  struct VsOut {
      @builtin(position) pos: vec4f,
      @location(0) color: vec3f,
  }

  @vertex
  fn vs_main(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VsOut {
      var p = array<vec3f, 36>(
          vec3f(-1,-1, 1), vec3f( 1,-1, 1), vec3f( 1, 1, 1),
          vec3f(-1,-1, 1), vec3f( 1, 1, 1), vec3f(-1, 1, 1),
          vec3f( 1,-1,-1), vec3f(-1,-1,-1), vec3f(-1, 1,-1),
          vec3f( 1,-1,-1), vec3f(-1, 1,-1), vec3f( 1, 1,-1),
          vec3f( 1,-1, 1), vec3f( 1,-1,-1), vec3f( 1, 1,-1),
          vec3f( 1,-1, 1), vec3f( 1, 1,-1), vec3f( 1, 1, 1),
          vec3f(-1,-1,-1), vec3f(-1,-1, 1), vec3f(-1, 1, 1),
          vec3f(-1,-1,-1), vec3f(-1, 1, 1), vec3f(-1, 1,-1),
          vec3f(-1, 1, 1), vec3f( 1, 1, 1), vec3f( 1, 1,-1),
          vec3f(-1, 1, 1), vec3f( 1, 1,-1), vec3f(-1, 1,-1),
          vec3f(-1,-1,-1), vec3f( 1,-1,-1), vec3f( 1,-1, 1),
          vec3f(-1,-1,-1), vec3f( 1,-1, 1), vec3f(-1,-1, 1),
      );

      var c = array<vec3f, 6>(
          vec3f(0.3, 0.5, 1.0),
          vec3f(1.0, 0.3, 0.3),
          vec3f(0.3, 1.0, 0.3),
          vec3f(1.0, 1.0, 0.3),
          vec3f(0.3, 1.0, 1.0),
          vec3f(1.0, 0.3, 1.0),
      );

      // 10x10x10 grid — decode instance index into 3D grid position

      let ix = f32(ii % 10u);
      let iy = f32((ii / 10u) % 10u);
      let iz = f32(ii / 100u);

      // Grid offset: center the grid, spacing of 3.0 units

      let spacing = 3.0;
      let grid_offset = vec3f(
          (ix - 4.5) * spacing,
          (iy - 4.5) * spacing,
          (iz - 4.5) * spacing,
      );

      // Per-instance rotation speed variation using instance index

      let seed = f32(ii) * 0.0037;
      let t = u.time + seed;
      let v = p[vi] * 0.8;

      let cy = cos(t); let sy = sin(t);
      let cx = cos(t * 0.7); let sx = sin(t * 0.7);

      let ry = vec3f(v.x*cy + v.z*sy, v.y, -v.x*sy + v.z*cy);
      let r  = vec3f(ry.x, ry.y*cx - ry.z*sx, ry.y*sx + ry.z*cx);

      // Translate to grid position

      let local = r + grid_offset;

      // Global rotation of the entire matrix

      let gt = u.time * 0.3;
      let gcy = cos(gt); let gsy = sin(gt);
      let gcx = cos(gt * 0.5); let gsx = sin(gt * 0.5);

      let gry = vec3f(local.x*gcy + local.z*gsy, local.y, -local.x*gsy + local.z*gcy);
      let world = vec3f(gry.x, gry.y*gcx - gry.z*gsx, gry.y*gsx + gry.z*gcx);

      let z = world.z + 60.0;
      let fov = 2.0;

      // Color tint based on grid position

      let tint = vec3f(ix / 9.0, iy / 9.0, iz / 9.0) * 0.6 + 0.4;

      var out: VsOut;
      out.pos = vec4f(world.x * fov / (z * u.aspect), world.y * fov / z, (world.z + 50.0) / 150.0, 1.0);
      out.color = c[vi / 6u] * tint;
      return out;
  }

  @fragment
  fn fs_main(@location(0) color: vec3f) -> @location(0) vec4f {
      return vec4f(color, 1.0);
  }
  
"

fn init_gpu() {
  let hwnd = win.native_handle()

  // Create surface FIRST, then request adapter compatible with it

  surface_h = wgpu.create_surface(hwnd)
  adapter_h = wgpu.request_adapter(#{power_preference: "high", compatible_surface: surface_h})
  let dq = adapter_h.request_device(#{})

  device_h = dq.device
  queue_h = dq.queue
  surface_h.configure(adapter_h, device_h, win_w, win_h)
  let fmt = surface_h.format()

  let shader = device_h.create_shader(#{label: "cube", source: shader_src})

  // Uniform buffer (16 bytes = 1 float + padding, aligned to 16)


  pipeline_h = device_h.create_render_pipeline(#{
    label: "cube-pipeline",
    vertex_shader: shader,
    vertex_entry: "vs_main",
    fragment_shader: shader,
    fragment_entry: "fs_main",
    color_format: fmt,
    depth_format: "depth32float",
  })
  uniform_buf_h = device_h.create_buffer(#{
    label: "uniforms",
    size: 16,
    usage: "uniform|copy_dst",
  })
  let bgl = pipeline_h.get_bind_group_layout(0)

  bind_group_h = device_h.create_bind_group(bgl, uniform_buf_h, 0)
  depth_view_h = device_h.create_depth_view(win_w, win_h)
  gpu_ready = true
  print("GPU initialized: {fmt}")
}

fn render() {
  frame_start = os.clock()
  frame_count = frame_count + 1
  let t = frame_count * 0.016

  // FPS counter — update every second

  fps_frames = fps_frames + 1
  let now = os.clock()
  if now - fps_last_time >= 1.0 {
    fps_current = fps_frames
    fps_frames = 0
    fps_last_time = now
    print("FPS: {fps_current}")
  }

  if frame_count == 1 {
    print("First render frame, t={t}")
  }

  let aspect = win_w * 1.0 / (win_h * 1.0)
  queue_h.write_floats(uniform_buf_h, 0, [t, aspect, 0.0, 0.0])

  let tex = surface_h.get_current_texture()
  let view = tex.create_view()

  let enc = device_h.create_command_encoder(#{label: "frame"})
  let pass = enc.begin_render_pass(#{
    color_attachments: #{
      "1": #{
        view,
        load: "clear",
        clear_color: get_clear_color(),
        store: "store",
      },
    },
    depth_view: depth_view_h,
  })

  sleep 16ms

  pass.set_pipeline(pipeline_h)
  pass.set_bind_group(0, bind_group_h)
  pass.draw(36, 1000, 0, 0)
  pass.end()

  let cmd = enc.finish()
  queue_h.submit(#{"1": cmd})
  view.destroy()
  tex.present()
  // Busy-wait for frame pacing precision

  // while os.clock() - frame_start < TARGET_FRAME_TIME {

  //   sleep 1ms

  // }

}

fn on_event(event) {
  match event.event {
    "redraw_requested" => {
      if !gpu_ready {
        init_gpu()
      }
      render()
    },
    "resized" => {
      if gpu_ready {
        let w = event.width
        let h = event.height
        if w > 0 && h > 0 {
          win_w = w
          win_h = h
          surface_h.configure(adapter_h, device_h, win_w, win_h)
          depth_view_h = device_h.create_depth_view(win_w, win_h)
        }
      }
    },
    "close_requested" => {
      print("Goodbye!")
    },
    _ => { },
  }
}

event_loop.run(on_event)

Requiere la CLI o el host de Zolo; ábrelo en el playground o ejecútalo localmente.

Servidor HTTP sin reiniciar

El demo 10-http-server inicia un servidor real en el puerto 8080. El módulo handlers.zolo define las rutas home y hello. Editar y guardar los handlers mientras el servidor corre reemplaza las funciones vía swap; la siguiente solicitud ya usa la versión nueva — el socket de escucha no se cierra.

cd examples/features/21-hot-reload/10-http-server
zolo dev main.zolo

Luego, en otra terminal:

curl http://localhost:8080/
curl http://localhost:8080/hello/zolo

Edita el campo "body" y haz curl de nuevo — la respuesta cambia de inmediato.

handlers.zolo
Playground
// Edit these handlers while the server is running. Each save triggers

// a hot-swap; the next request uses the new code.


pub fn home(req) {
  return #{
    "status": 200,
    "body":   "<h1>Zolo Live</h1><p>Edit handlers.zolo and refresh.</p>",
  }
}

pub fn hello(req) {
  let name = req.params.name
  return #{
    "status": 200,
    "body":   "Hi, {name}! (V1)",
  }
}

Requiere la CLI o el host de Zolo; ábrelo en el playground o ejecútalo localmente.

main.zolo
Playground
// Demo 06 — Hot-reload an HTTP server's route handlers.

//

//   zolo dev examples/zolo_live/06_http_server/main.zolo

//

// Starts a real HTTP server on port 8080. Edit handlers.zolo while the

// server is running; the VM instruction hook drains pending swaps

// between requests, so the next incoming request hits the new handler

// — without restarting the server, dropping the listener socket, or

// losing in-flight state.

//

// Test from another terminal:

//   curl http://localhost:8080/

//   curl http://localhost:8080/hello/world

//

// Then edit handlers.zolo and re-run the curl: see the new response.


use std::http::{Server, Router}
use handlers::{home, hello}

fn main() {
  let router = Router.new()
  router.get("/", home)
  router.get("/hello/:name", hello)

  let server = Server.new("0.0.0.0:8080", router)
  print("Listening on http://localhost:8080 (edit handlers.zolo to hot-reload)")
  server.run()
}

Requiere la CLI o el host de Zolo; ábrelo en el playground o ejecútalo localmente.

Desafío

En handlers.zolo, agrega una nueva ruta pub fn about(req) que retorne información de versión. Luego, en main.zolo, registra router.get("/about", about). Guarda ambos archivos y verifica con curl http://localhost:8080/about que la ruta aparece sin reiniciar el servidor.

Buscar en Zolo

9 resultados

enespt-br