Skip to content

Commit

Permalink
WASM bindings for demo
Browse files Browse the repository at this point in the history
  • Loading branch information
scheibo committed Nov 6, 2024
1 parent e8ebc83 commit 059a059
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 36 deletions.
8 changes: 7 additions & 1 deletion src/tools/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {Smogon} from '@pkmn/smogon';
import {Battle, Choice, Lookup, initialize} from '../pkg';

import {Move, Species, pruneMove, pruneSpecies, render} from './display';
import {imports} from './display/util';

const ROOT = path.resolve(__dirname, '..', '..');

Expand Down Expand Up @@ -90,7 +91,12 @@ const SKIP = ['gen1lc'] as ID[];
const bytes = await fs.readFile(file);
const wasm = bytes.toString('base64');

await initialize(showdown, new WebAssembly.Module(bytes));
const memory: [WebAssembly.Memory] = [null!];
const decoder = new TextDecoder();
const instance =
await WebAssembly.instantiate(new WebAssembly.Module(bytes), imports(memory, decoder));
memory[0] = instance.exports.memory as WebAssembly.Memory;
await initialize(showdown, instance);
const battle = Battle.create(gen, {
p1: {team: [p1!]}, p2: {team: [p2!]}, seed: [1, 2, 3, 4], showdown, log: false,
});
Expand Down
119 changes: 94 additions & 25 deletions src/tools/demo.zig
Original file line number Diff line number Diff line change
@@ -1,13 +1,49 @@
const pkmn = @import("pkmn");
const std = @import("std");

const Allocator = std.mem.Allocator;
const allocator = std.heap.wasm_allocator;
const assert = std.debug.assert;
const Choice = pkmn.Choice;
const protocol = pkmn.protocol;
const Rational = pkmn.Rational;
const wasm = pkmn.bindings.wasm;

const js = struct {
extern "js" fn log(ptr: [*]const u8, len: usize) void;
extern "js" fn panic(ptr: [*]const u8, len: usize) noreturn;
};

pub const std_options = if (@hasDecl(std, "Options")) std.Options{
.logFn = log,
.log_level = .debug,
} else struct {
pub const logFn = log;
pub const log_level = .debug;
};

fn log(
comptime message_level: std.log.Level,
comptime scope: @TypeOf(.enum_literal),
comptime format: []const u8,
args: anytype,
) void {
const level_txt = comptime message_level.asText();
const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
var buf: [500]u8 = undefined;
const line = std.fmt.bufPrint(&buf, level_txt ++ prefix2 ++ format, args) catch l: {
buf[buf.len - 3 ..][0..3].* = "...".*;
break :l &buf;
};
js.log(line.ptr, line.len);
}

pub fn panic(msg: []const u8, st: ?*std.builtin.StackTrace, addr: ?usize) noreturn {
_ = st;
_ = addr;
std.log.err("panic: {s}", .{msg});
@trap();
}

pub const pkmn_options = pkmn.Options{
.advance = false,
.ebc = false,
Expand All @@ -17,29 +53,29 @@ pub const pkmn_options = pkmn.Options{

const gen1 = struct {
const Actions = pkmn.gen1.chance.Actions;
const Battle = pkmn.gen1.data.Battle;
const Battle = pkmn.gen1.Battle;
const Calc = pkmn.gen1.Calc;
const Chance = pkmn.gen1.Chance;
const Durations = pkmn.gen1.chance.Durations;
const PRNG = pkmn.gen1.data.PRNG;
const PRNG = pkmn.gen1.PRNG;
const Rolls = pkmn.gen1.calc.Rolls;

const Result = struct {
actions: Actions,
probability: Rational(u128),
};

pub fn transitions(
battle: Battle(.PRNG),
battle: Battle(PRNG),
c1: Choice,
c2: Choice,
allocator: Allocator,
durations: ?Durations,
d: Durations,
cap: bool,
seen: *std.AutoArrayHashMap(Actions, Rational(u128)),
) !void {
const cap = true; // FIXME

var seen = std.AutoHashMap(Actions, void).init(allocator);
defer seen.deinit();
var frontier = std.ArrayList(Actions).init(allocator);
defer frontier.deinit();

const d = durations orelse .{};

var opts = pkmn.battle.options(
protocol.NULL,
Chance(Rational(u128)){ .probability = .{}, .durations = d },
Expand All @@ -52,7 +88,6 @@ const gen1 = struct {
const p1 = b.side(.P1);
const p2 = b.side(.P2);

var p: Rational(u128) = .{ .p = 0, .q = 1 };
try frontier.append(opts.chance.actions);

// zig fmt: off
Expand Down Expand Up @@ -110,7 +145,6 @@ const gen1 = struct {
opts.calc.overrides = a;
opts.calc.summaries = .{};
opts.chance = .{ .probability = .{}, .durations = d };
const q = &opts.chance.probability;

b = battle;
_ = try b.update(c1, c2, &opts);
Expand All @@ -135,15 +169,11 @@ const gen1 = struct {
var acts = opts.chance.actions;
acts.p1.damage = @intCast(p1d);
acts.p2.damage = @intCast(p2d);
assert(!try seen.getOrPut(acts).found_existing);
const v = try seen.getOrPut(acts);
assert(!v.found_existing);
v.value_ptr.* = opts.chance.probability;
}
}
if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);

q.reduce();
try p.add(q);
p.reduce();
} else if (!opts.chance.actions.matchesAny(frontier.items, i)) {
try frontier.append(opts.chance.actions);
}
Expand All @@ -156,11 +186,23 @@ const gen1 = struct {
}
frontier.shrinkRetainingCapacity(1);
// zig fmt: on

p.reduce();
}
};

fn Slice(T: type) type {
return packed struct(u64) {
ptr: u32,
len: u32,

fn init(s: []const T) @This() {
return .{
.ptr = @intFromPtr(s.ptr),
.len = s.len,
};
}
};
}

export const SHOWDOWN = wasm.options.showdown;
export const LOG = wasm.options.log;
export const CHANCE = wasm.options.chance;
Expand All @@ -176,6 +218,33 @@ usingnamespace if (exportable) struct {
export const GEN1_choices = wasm.gen(1).choices;
} else wasm.exports();

export fn GEN1_demo(n: u32) u32 {
return n + 1;
export fn GEN1_transitions(
battle: *gen1.Battle(gen1.PRNG),
c1: Choice,
c2: Choice,
durations: gen1.Durations,
cap: bool,
) Slice(gen1.Result) {
var seen = std.AutoArrayHashMap(gen1.Actions, Rational(u128)).init(allocator);
defer seen.deinit();

gen1.transitions(battle.*, c1, c2, durations, cap, &seen) catch |err| switch (err) {
error.OutOfMemory => @panic("out of memory"),
error.Overflow => @panic("overflow"),
else => unreachable,
};

var results = allocator.alloc(gen1.Result, seen.count()) catch @panic("out of memory");
var it = seen.iterator();
var i: usize = 0;
while (it.next()) |kv| {
results[i] = .{ .actions = kv.key_ptr.*, .probability = kv.value_ptr.* };
std.log.debug("{s} = {s}", .{ results[i].actions, results[i].probability });
i += 1;
}
return Slice(gen1.Result).init(results);
}

export fn GEN1_transitions_deinit(results: Slice(gen1.Result)) void {
allocator.free(@as([*]gen1.Result, @ptrFromInt(results.ptr))[0..results.len]);
}
69 changes: 59 additions & 10 deletions src/tools/display/demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,43 @@ import * as gen1 from '../../pkg/gen1';

// import {Select} from './select';
import {Battle, Gen, Generation, adapt} from './ui';
import {imports} from './util';

const App = ({gen, data, showdown}: {gen: Generation; data: DataView; showdown: boolean}) => {
function toBinding(gen: number, w: WebAssembly.Exports) {
const prefix = `GEN${gen}`;
const buf = (w.memory as WebAssembly.Memory).buffer;

const transitions = w[`${prefix}_transitions`] as CallableFunction;
const deinit = w[`${prefix}_transitions_deinit`] as CallableFunction;
const memory = new Uint8Array(buf);

return {
transitions(
this: void,
battle: ArrayBuffer,
c1: number,
c2: number,
durations: bigint,
cap: boolean,
): bigint {
const bytes = new Uint8Array(battle);
memory.set(bytes, 0);

return transitions(0, c1, c2, durations, cap);
},

deinit(this: void, ref: bigint) {
deinit(ref);
},
};
}

const App = ({gen, data, showdown, instance}: {
gen: Generation;
data: DataView;
showdown: boolean;
instance: WebAssembly.Instance;
}) => {
const lookup = engine.Lookup.get(gen);
const deserialize = (d: DataView): engine.Battle => {
switch (gen.num) {
Expand All @@ -13,6 +48,15 @@ const App = ({gen, data, showdown}: {gen: Generation; data: DataView; showdown:
}
};
const battle = deserialize(data);
const {transitions, deinit} = toBinding(gen.num, instance.exports);

const durations = 0n; // TODO
const move = engine.Choice.encode(engine.Choice.move(1));
const results =
transitions(battle.bytes().buffer, move, move, durations, true);
console.debug(results);
deinit(results);

return <Battle battle={battle} gen={gen} showdown={showdown} hide={true} />;
};

Expand Down Expand Up @@ -43,15 +87,20 @@ console.debug(order);

const bytes = Uint8Array.from(atob(wasm), c => c.charCodeAt(0));
const mod = new WebAssembly.Module(bytes);
const instantiate = WebAssembly.instantiate(mod);
Promise.all([instantiate, engine.initialize(json.showdown, mod)]).then(([instance]) => {
console.debug((instance.exports.GEN1_demo as CallableFunction)(41));
const buf = Uint8Array.from(atob(json.buf), c => c.charCodeAt(0));
document.getElementById('content')!.appendChild(<App
gen={GEN}
data={new DataView(buf.buffer, buf.byteOffset, buf.byteLength)}
showdown={json.showdown}
/>);

const memory: [WebAssembly.Memory] = [null!];
const decoder = new TextDecoder();
WebAssembly.instantiate(mod, imports(memory, decoder)).then(instance => {
memory[0] = instance.exports.memory as WebAssembly.Memory;
return engine.initialize(json.showdown, instance).then(() => {
const buf = Uint8Array.from(atob(json.buf), c => c.charCodeAt(0));
document.getElementById('content')!.appendChild(<App
gen={GEN}
data={new DataView(buf.buffer, buf.byteOffset, buf.byteLength)}
showdown={json.showdown}
instance={instance}
/>);
});
}).catch(console.error);

// const select = <Select options={order.species} placeholder='Tauros' />;
Expand Down
14 changes: 14 additions & 0 deletions src/tools/display/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,17 @@ export const toText = (parsed: ParsedLine[]) =>
export const pretty = (choice?: Choice) => choice
? choice.type === 'pass' ? choice.type : `${choice.type} ${choice.data}`
: '???';

export const imports = (memory: [WebAssembly.Memory], decoder: TextDecoder) => ({
js: {
log(ptr: number, len: number) {
if (len === 0) return console.log('');
const msg = decoder.decode(new Uint8Array(memory[0].buffer, ptr, len));
console.log(msg);
},
panic(ptr: number, len: number) {
const msg = decoder.decode(new Uint8Array(memory[0].buffer, ptr, len));
throw new Error('panic: ' + msg);
},
},
});

0 comments on commit 059a059

Please sign in to comment.