From 25a431f7dee03ce1b25e82f0daa292f9bf822221 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Sat, 31 Aug 2024 23:03:19 +0530 Subject: [PATCH] Add Canvas.loadFontRaw and Surface.fromRaw (#76) --- client.ts | 12 ++++++ examples/sprite/client.ts | 0 examples/sprite/main.ts | 79 ++++++++++++++++++++++++++++----------- examples/sprite/util.ts | 13 +++---- mod.ts | 56 ++++++++++++++++++++++++++- 5 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 client.ts create mode 100644 examples/sprite/client.ts diff --git a/client.ts b/client.ts new file mode 100644 index 0000000..99677ed --- /dev/null +++ b/client.ts @@ -0,0 +1,12 @@ +if ( + typeof WorkerGlobalScope === "undefined" || + !(self instanceof WorkerGlobalScope) +) { + const selfPath = new URL(import.meta.url).pathname; + const worker = new Worker(`file://${selfPath}`, { type: "module" }); +} else { + const ws = new WebSocket("ws://localhost:1234"); + self.onmessage = (event) => { + ws.send(event.data); + }; +} diff --git a/examples/sprite/client.ts b/examples/sprite/client.ts new file mode 100644 index 0000000..e69de29 diff --git a/examples/sprite/main.ts b/examples/sprite/main.ts index e02dc32..3807ebb 100644 --- a/examples/sprite/main.ts +++ b/examples/sprite/main.ts @@ -1,7 +1,16 @@ import { EventType, Rect, Surface, WindowBuilder } from "../../mod.ts"; -import { drawMap, sleepSync, Sprite } from "./util.ts"; +import { drawMap, Sprite } from "./util.ts"; -const canvasSize = { width: 400, height: 400 }; +function sleepSync(ms: number) { + const start = Date.now(); + while (true) { + if (Date.now() - start > ms) { + break; + } + } +} + +const canvasSize = { width: 1000, height: 800 }; const window = new WindowBuilder( "Hello, Deno!", canvasSize.width, @@ -15,14 +24,22 @@ const creator = canv.textureCreator(); const texture = creator.createTextureFromSurface(surface); const map = [ - [8, 8, 9, 8, 11, 8, 8, 8], - [8, 8, 8, 8, 8, 8, 8, 8], - [8, 10, 8, 8, 8, 8, 8, 8], - [8, 8, 8, 8, 8, 8, 8, 8], - [8, 8, 8, 8, 8, 8, 10, 8], - [8, 8, 8, 8, 8, 9, 8, 8], - [10, 8, 8, 8, 8, 8, 8, 8], - [8, 8, 11, 8, 8, 8, 8, 8], + [8, 8, 9, 8, 11, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 8, 8], + [8, 10, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 10, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8], + [10, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 8, 8], + [8, 8, 11, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8], + [8, 8, 9, 8, 11, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 8, 8], + [8, 10, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8], + [8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 8], + [8, 8, 8, 8, 8, 8, 10, 8, 8, 8, 8, 8, 8, 8, 8], + [8, 8, 8, 8, 8, 9, 8, 8, 8, 8, 8, 8, 8, 8, 8], + [10, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8, 8, 8, 8], + [8, 8, 11, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 10, 8], ]; const denoTextureFrames = [ @@ -52,33 +69,31 @@ function createShadowInstance() { return shadow; } -function createDenoInstance() { +function createDenoInstance(id) { const deno = new Sprite(texture, denoTextureFrames); deno.x = random(0, canvasSize.width); deno.y = random(0, canvasSize.height); deno.originX = deno.frames[0].width / 2; deno.originY = deno.frames[0].height; deno.scale = 4; - deno.vx = 2; - deno.vy = 1; + deno.vx = 0; + deno.vy = 0; + deno.id = id || 0; return deno; } const denos: Sprite[] = []; - -for (let i = 0; i < 1; i++) { - denos.push(createDenoInstance()); -} - +const self = createDenoInstance(); const shadow = createShadowInstance(); let cnt = 0; +let mouse = { x: 0, y: 0 }; -function frame() { +function frame(e) { canv.clear(); - drawMap(texture, canv, map, 16); + const tiles = drawMap(texture, canv, map, 16); - for (const deno of denos) { + for (const deno of [...denos, self]) { deno.tick(); shadow.draw(canv); deno.draw(canv); @@ -111,10 +126,26 @@ function frame() { } } - cnt++; + deno.vx = (mouse.x - deno.x) / 100; + deno.vy = (mouse.y - deno.y) / 100; + + // check for collision with tiles on map + for (const tile of tiles) { + if ( + deno.x < tile.x + tile.width && + deno.x + deno.frames[0].width * deno.scale > tile.x && + deno.y < tile.y + tile.height && + deno.y + deno.frames[0].height * deno.scale > tile.y + ) { + deno.x -= deno.vx; + deno.y -= deno.vy; + } + } } + cnt++; canv.present(); + sleepSync(10); } @@ -126,6 +157,10 @@ for await (const event of window.events()) { case EventType.Quit: Deno.exit(0); break; + case EventType.MouseMotion: + mouse.x = event.x; + mouse.y = event.y; + break; default: break; } diff --git a/examples/sprite/util.ts b/examples/sprite/util.ts index 33cc44d..a97b2a3 100644 --- a/examples/sprite/util.ts +++ b/examples/sprite/util.ts @@ -1,11 +1,5 @@ import { Canvas, Rect, Texture } from "../../mod.ts"; -export function sleepSync(timeout: number) { - const sab = new SharedArrayBuffer(1024); - const int32 = new Int32Array(sab); - Atomics.wait(int32, 0, 0, timeout); -} - export interface Area { x: number; y: number; @@ -18,7 +12,8 @@ export function drawMap( canvas: Canvas, map: number[][], chipSize: number, -) { +): Rect[] { + const frames: Rect[] = []; for (let i = 0; i < map.length; i++) { for (let j = 0; j < map[i].length; j++) { const chip = map[i][j]; @@ -34,6 +29,9 @@ export function drawMap( chipSize * 4, chipSize * 4, ); + // 9 = cactus + // 8 = normal tile + if (chip === 9) frames.push(dst); canvas.copy( texture, src, @@ -41,6 +39,7 @@ export function drawMap( ); } } + return frames; } export class Sprite { diff --git a/mod.ts b/mod.ts index 9231e72..4a9b9eb 100644 --- a/mod.ts +++ b/mod.ts @@ -286,6 +286,10 @@ const sdl2 = Deno.dlopen(getLibraryPath("SDL2"), { "parameters": [], "result": "i32", }, + "SDL_RWFromMem": { + "parameters": ["buffer", "i32"], + "result": "pointer", + }, }); const SDL2_Image_symbols = { @@ -297,6 +301,10 @@ const SDL2_Image_symbols = { "parameters": ["buffer"], "result": "pointer", }, + "IMG_Load_RW": { + "parameters": ["pointer"], + "result": "pointer", + }, } as const; const SDL2_TTF_symbols = { @@ -308,6 +316,10 @@ const SDL2_TTF_symbols = { "parameters": ["buffer", "i32"], "result": "pointer", }, + "TTF_OpenFontRW": { + "parameters": ["pointer", "i32", "i32"], + "result": "pointer", + }, "TTF_RenderText_Solid": { "parameters": ["pointer", "buffer", "pointer"], "result": "pointer", @@ -410,6 +422,7 @@ function init() { } init(); + /** * An enum that contains structures for the different event types. */ @@ -486,6 +499,15 @@ export class Canvas { private target: Deno.PointerValue, ) {} + serialize(): ArrayBuffer { + return new BigUint64Array([Deno.UnsafePointer.value(this.target)]).buffer; + } + + static deserialize(data: ArrayBuffer): Canvas { + const [ptr] = new BigUint64Array(data); + return new Canvas(null!, Deno.UnsafePointer.create(ptr)); + } + /** * Set the color used for drawing operations (Rect, Line and Clear). * @param r the red value used to draw on the rendering target @@ -679,6 +701,18 @@ export class Canvas { const raw = sdl2Font.symbols.TTF_OpenFont(asCString(path), size); return new Font(raw); } + + loadFontRaw(data: Uint8Array, size: number): Font { + const rwops = sdl2.symbols.SDL_RWFromMem(data, data.byteLength); + if (rwops === null) { + throwSDLError(); + } + const raw = sdl2Font.symbols.TTF_OpenFontRW(rwops, 1, size); + if (raw === null) { + throwSDLError(); + } + return new Font(raw); + } } /** @@ -1015,6 +1049,20 @@ export class Surface { } return new Surface(raw); } + + static fromRaw(data: Uint8Array): Surface { + if (!sdl2Image) { + throw new Error("SDL2_image was not loaded"); + } + + const rwops = sdl2.symbols.SDL_RWFromMem(data, data.byteLength); + const raw = sdl2Image.symbols.IMG_Load_RW(rwops); + if (raw === null) { + throwSDLError(); + } + + return new Surface(raw); + } } const sizeOfEvent = 56; // type (u32) + event @@ -1332,7 +1380,7 @@ export class Window { /** * Events from the window. */ - *events(wait = false): Generator { + async *events(wait = false): AsyncGenerator { while (true) { const event = Deno.UnsafePointer.of(eventBuf); @@ -1342,6 +1390,12 @@ export class Window { const pending = (shouldWait ? sdl2.symbols.SDL_WaitEvent(event) : sdl2.symbols.SDL_PollEvent(event)) == 1; + if (shouldWait) { + // Run microtasks. + await new Promise((resolve) => + setTimeout(resolve, 0) + ); + } if (!pending) { yield { type: EventType.Draw }; }