Pular para o conteúdo

Loop de Render e Servidor HTTP

Nesta página

Loop de renderização com wgpu

O demo 07-render-loop mostra um loop de evento winit que renderiza 1000 cubos em grade 10×10×10 com instancing GPU. O módulo separado config.zolo expõe get_clear_color() — basta editá-lo e salvar enquanto a janela está aberta para ver a cor de fundo mudar no próximo frame, sem reinicializar o pipeline.

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

Altere os valores r/g/b aqui para mudar a cor de fundo em tempo real.

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

Requer a CLI/host do Zolo — abra no playground ou rode localmente.

Loop de evento winit + wgpu: init_gpu(), render() e on_event() com 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)

Requer a CLI/host do Zolo — abra no playground ou rode localmente.

Servidor HTTP sem reiniciar

O demo 10-http-server inicia um servidor real na porta 8080. O módulo handlers.zolo define as rotas home e hello. Editar e salvar os handlers enquanto o servidor roda substitui as funções via swap; a próxima requisição já usa a versão nova — o socket de escuta não é fechado.

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

Depois, em outro terminal:

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

Edite o campo "body" e faça o curl de novo — a resposta muda imediatamente.

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)",
  }
}

Requer a CLI/host do Zolo — abra no playground ou rode 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()
}

Requer a CLI/host do Zolo — abra no playground ou rode localmente.

Desafio

No handlers.zolo, adicione uma nova rota pub fn about(req) que retorna informações da versão. Depois, em main.zolo, registre router.get("/about", about). Salve ambos os arquivos e verifique com curl http://localhost:8080/about que a rota aparece sem reiniciar o servidor.

Buscar no Zolo

9 resultados

enespt-br