Ollin

RoomCanvas

one piece across several machines: two Macs on the same network find each other by the room's name alone, share every parameter, take one seat each so the beads travel across a wall as wide as the room, and run on one shared clock instead of each machine's own. Run alone, it is one seat of one (Room, room.time, room.seat)

Run
swift run --package-path Examples Example-Integration-RoomCanvas
Imports
OllinRoom
Source
Examples/Integration/RoomCanvas/Sketch.swift
import Foundation
import Ollin
import OllinRoom

/// One piece across several machines. Open this sketch on two Macs on the same
/// network and they find each other by the room's name alone: no server, no
/// address to type, nothing to configure.
///
/// What the room gives the sketch is the whole demonstration:
///
/// - **One clock.** The beads travel along a wall that is `seatCount` canvases
///   wide, driven by `room.time` rather than the sketch's own `time`. Each
///   machine starts whenever it starts, and they still move as one.
/// - **One seat each.** A machine draws the whole wall and slides it by its own
///   seat, so screen 0 shows the left canvas and screen 1 the next one along.
///   Ask for a fixed seat with `Room(named: "wall", seat: 1)` when the machines
///   stand in a known order.
/// - **Shared parameters.** Every `@Param` below travels, so changing `speed` on any
///   machine turns it on all of them.
///
/// With one machine it still runs: one seat, one canvas, and the readout says
/// the room is empty. The system asks for permission to use the local network
/// the first time a sketch opens a room, and a sketch run from a terminal
/// inherits the terminal's answer.
@main
final class RoomCanvas: Sketch {

    @Param(0.2...3.0, group: "Motion") var speed = 1.0
    @Param(4...40, group: "Motion") var beads = 16
    @Param(0.2...1.4, group: "Look") var weight = 0.7
    @Param(group: "Look") var ink = Color(red: 0.98, green: 0.36, blue: 0.24)

    let room = Room(named: "wall")

    private let paper = Color(red: 0.07, green: 0.08, blue: 0.11)

    override func setup() {
        // Every parameter travels, so whoever is standing next to a machine can change
        // one and the whole room follows.
        room.shareAll()
        extend(room)
    }

    override func draw() {
        background(paper)

        let seats = Double(room.seatCount)
        let wall = width * seats

        withState {
            // Each machine draws the whole wall and slides its own seat into view.
            translate(-Double(room.seat) * width, 0)
            drawWall(width: wall)
        }

        drawSeatEdges()
        drawReadout()
    }

    /// The piece itself: a run of beads crossing the whole wall, and a rope
    /// threaded through them.
    private func drawWall(width wall: Double) {
        let clock = room.time * speed
        var path: [Vector2] = []
        for index in 0..<beads {
            let phase = Double(index) / Double(beads)
            let x = ((clock * 0.12 + phase).truncatingRemainder(dividingBy: 1)) * wall
            let y = height * (0.5 + 0.28 * sin(clock * 0.9 + phase * 6.283))
            path.append(Vector2(x, y))
        }

        noFill()
        stroke(ink.withAlpha(0.35))
        strokeWeight(2 * weight)
        for index in 1..<path.count {
            // A bead that wrapped around the end has no line back to the start.
            if path[index].x > path[index - 1].x {
                drawLine(path[index - 1], path[index])
            }
        }

        noStroke()
        for (index, point) in path.enumerated() {
            let phase = Double(index) / Double(beads)
            fill(ink.withAlpha(0.55 + 0.45 * phase))
            drawCircle(center: point, radius: 12 + 26 * weight * (0.4 + 0.6 * phase))
        }
    }

    /// A hairline down each seam, so a wall of screens shows where one machine
    /// ends and the next begins.
    private func drawSeatEdges() {
        guard room.seatCount > 1 else { return }
        stroke(Color.white.withAlpha(0.12))
        strokeWeight(1)
        drawLine(0, 0, 0, height)
        drawLine(width, 0, width, height)
    }

    /// Who is here, which seat this machine has, and how well the clocks agree.
    private func drawReadout() {
        let scale = width / 1080
        let margin = 40 * scale

        noStroke()
        fill(Color.white.withAlpha(0.75))
        textSize(24 * scale)
        drawText("room: wall   seat \(room.seat + 1) of \(room.seatCount)", margin, margin + 24 * scale)

        textSize(19 * scale)
        fill(Color.white.withAlpha(0.5))
        let company = room.isAlone ? "alone: open this sketch on another Mac" : room.peers.joined(separator: ", ")
        drawText("\(room.peerName)   |   \(company)", margin, margin + 56 * scale)

        let agreement: String
        if let error = room.clockError {
            agreement = room.ownsClock ? "this machine keeps the clock" : String(format: "clock within %.0f ms", error * 1000)
        } else {
            agreement = "asking for the time"
        }
        drawText(agreement, margin, margin + 84 * scale)
        if let problem = room.lastError {
            fill(Color(red: 1, green: 0.5, blue: 0.4))
            drawText(problem, margin, margin + 112 * scale)
        }

        // The hand turns once every four seconds on the room's clock, so two
        // machines side by side show it in the same place.
        let hand = room.time * (.pi / 2)
        let center = Vector2(width - margin - 44 * scale, margin + 44 * scale)
        noFill()
        stroke(Color.white.withAlpha(0.25))
        strokeWeight(2 * scale)
        drawCircle(center: center, radius: 40 * scale)
        stroke(ink)
        drawLine(center, center + Vector2(cos(hand), sin(hand)) * (34 * scale))
    }
}

↑↓ move ↩ open