Skip to content

Commit

Permalink
async iterator based API (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
littledivy committed Aug 25, 2021
1 parent d52a994 commit c84a174
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 96 deletions.
30 changes: 21 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Have fun!

#### Features

- Bindings to Video, Graphics, Font and Mixer subsystems.
- API similar to `Rust-sdl2`
- Bindings to Video, Graphics, Font and Mixer subsystems. (Uses rodio instead of
SDL2_Mixer)
- Not `--unstable`. Uses TCP instead of Deno's traditional plugin system.

#### Example
Expand All @@ -18,17 +18,29 @@ import { Canvas } from "https://deno.land/x/sdl2/mod.ts";

const canvas = new Canvas({ title: "Hello, Deno!", width: 800, height: 400 });

canvas.addEventListener("event", (e: WindowEvent) => {
console.log(e.detail);
});

canvas.setDrawColor(0, 64, 255, 0);
canvas.setDrawColor(0, 64, 255, 255);
canvas.clear();
canvas.present();

canvas.start();
for await (const event of canvas) {
switch (event.type) {
case "draw":
// Your game logic
// ...
break;
case "mouse_motion":
// Mouse stuff
break;
case "key_down":
// Keyboard stuff
break;
// ...
default:
break;
}
}
```

### License

- MIT
MIT
159 changes: 72 additions & 87 deletions src/canvas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { EventEmitter } from "https://deno.land/x/[email protected]/mod.ts";
import { exists } from "https://deno.land/x/[email protected]/fs/exists.ts";
import { decodeConn, encode, readStatus } from "./msg.ts";
import { PixelFormat } from "./pixel.ts";
Expand Down Expand Up @@ -69,17 +68,12 @@ type Event = {
direction: number;
} | { type: "unknown" };

type WindowEvent = {
event: [Event];
draw: [];
};

export interface Rect extends Point {
width: number;
height: number;
}

export class Canvas extends EventEmitter<WindowEvent> {
export class Canvas {
#properties: WindowOptions;
// Used internally. Too lazy to define types
#tasks: any[] = [];
Expand All @@ -90,7 +84,6 @@ export class Canvas extends EventEmitter<WindowEvent> {
#closed = true;

constructor(properties: WindowOptions) {
super();
this.#properties = properties;
["centered", "fullscreen", "hidden", "resizable", "minimized", "maximized"]
.forEach((opt) => {
Expand Down Expand Up @@ -365,79 +358,78 @@ export class Canvas extends EventEmitter<WindowEvent> {
}

/**
* Start the event. Under the hood, it fires up the Rust client, polls for events and send tasks.
* Start the event loop. Under the hood, it fires up the Rust client, polls for events and send tasks.
* This function blocks rest of the JS event loop.
*
* Downloads the `deno_sdl2` client from Github releases on the first run.
*/
async start() {
async *[Symbol.asyncIterator]() {
this.#closed = false;
init(async (conn) => {
const window = encode(this.#properties);
await conn.write(window);

const videoReqBuf = await readStatus(conn);

switch (videoReqBuf) {
case 1:
// CANVAS_READY
const canvas = encode({
software: false,
});
await conn.write(canvas);
// SDL event_pump
event_loop:
while (true) {
const canvasReqBuf = await readStatus(conn);
switch (canvasReqBuf) {
case 1:
// CANVAS_LOOP_ACTION
const tasks = encode(this.#tasks);
await conn.write(tasks);
if (this.#closed) {
conn.close();
break event_loop;
}
this.#tasks = [];
break;
case 2:
// EVENT_PUMP
await decodeConn(conn).then((e: any) => {
e.forEach((ev: any) => {
const type = typeof ev == "string"
? ev
: Object.keys(ev)[0];
this.emit("event", { type, ...ev[type] });
});
});

break;
// TODO(@littledivy): Personally would love to have this <3
// case 5:
// // AUDIO_CALLBACK
// const eventLengthBuffer = new Uint8Array(4);
// await conn.read(eventLengthBuffer);
// const view = new DataView(eventLengthBuffer.buffer, 0);
// const eventLength = view.getUint32(0, true);
// const buf = new Float32Array(eventLength);

// this.#audioCallback(buf);
// await conn.write(new Uint8Array([0, 0]))
// await conn.write(encode((Array.from(buf))));

// break;
default:
break;
}

this.emit("draw");
const conn = await init();

const window = encode(this.#properties);
await conn.write(window);

const videoReqBuf = await readStatus(conn);

switch (videoReqBuf) {
case 1:
// CANVAS_READY
const canvas = encode({
software: false,
});
await conn.write(canvas);
// SDL event_pump
event_loop:
while (true) {
const canvasReqBuf = await readStatus(conn);
switch (canvasReqBuf) {
case 1:
// CANVAS_LOOP_ACTION
const tasks = encode(this.#tasks);
await conn.write(tasks);
if (this.#closed) {
conn.close();
break event_loop;
}
this.#tasks = [];
break;
case 2:
// EVENT_PUMP
const rawEvents: any[] = await decodeConn(conn);
for (const event of rawEvents) {
const type = typeof event == "string"
? event
: Object.keys(event)[0];
yield { type, ...event[type] };
}

break;
// TODO(@littledivy): Personally would love to have this <3
// case 5:
// // AUDIO_CALLBACK
// const eventLengthBuffer = new Uint8Array(4);
// await conn.read(eventLengthBuffer);
// const view = new DataView(eventLengthBuffer.buffer, 0);
// const eventLength = view.getUint32(0, true);
// const buf = new Float32Array(eventLength);

// this.#audioCallback(buf);
// await conn.write(new Uint8Array([0, 0]))
// await conn.write(encode((Array.from(buf))));

// break;
default:
break;
}
break;
// TODO(littledivy): CANVAS_ERR
default:
break;
}
});

yield { type: "draw" };
}
break;
// TODO(littledivy): CANVAS_ERR
default:
break;
}
}
}

Expand Down Expand Up @@ -481,15 +473,11 @@ async function downloadRelease() {
}
}

async function init(
cb: (conn: Deno.Conn) => Promise<void>,
// TODO(@littledivy): Make this toggleable with a build script?
dev: boolean = false,
) {
if (!dev) await downloadRelease();
async function init(): Promise<Deno.Conn> {
await downloadRelease();
const listener = Deno.listen({ port: 34254, transport: "tcp" });
const process = Deno.run({
cmd: [dev ? "target/debug/deno_sdl2" : "./deno_sdl2"],
cmd: ["./deno_sdl2"],
stderr: "inherit",
stdout: "inherit",
});
Expand All @@ -498,11 +486,8 @@ async function init(
switch (reqBuf) {
case 0:
// VIDEO_READY
await cb(conn);
break;
return conn;
default:
break;
throw new TypeError("Invalid request");
}

await process.status();
}

0 comments on commit c84a174

Please sign in to comment.