Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
329 changes: 308 additions & 21 deletions compiler/backend/emit.ts

Large diffs are not rendered by default.

31 changes: 27 additions & 4 deletions compiler/backend/graph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
* where every value lives by writing such fields, which is the point of having one.
*/
import type { At, Stmt } from "../ir";
import type { ScriptString } from "../runtime";
import type { ActionRecord, ConditionRecord } from "../../vendor/triggers";

/** A dword the graph reads or writes. */
export type Dword =
Expand Down Expand Up @@ -57,7 +59,18 @@ export interface FixedFn { fn: "fmul_vars" | "fdiv_vars" | "fmul_by" | "fdiv_by"
* destination or modifier, the mask of a masked one (a condition's or an action's, which it reads as it runs: what the
* mask is can be written like any number), the next pointer.
*/
export type Field = { cond: number; part?: "value" | "player" | "mask" } | { act: number; part: "value" | "dest" | "modifier" | "mask" } | "next";
/**
* (An action of the game's, `Act.record`, names its other fields by these too: its location is where the mask is, its
* first player the destination, its second player or amount the number, its unit type, kind and count the modifier's
* dword; `text`, `wav` and `time` are its own.)
*/
/**
* `current`: the dword whose top byte is the trigger's current action (where a trigger held by a Wait or a
* Transmission goes on from); an action's `flags` dword has its flags in the low byte (bit 0: a Wait or a Transmission
* started, which the game steps over the next time it is reached).
*/
export type Field = { cond: number; part?: "value" | "player" | "mask" } | { act: number; part: ActPart } | "next" | "current";
export type ActPart = "value" | "dest" | "modifier" | "mask" | "text" | "wav" | "time" | "flags";

export type Compare = "atLeast" | "atMost" | "exactly";
export type Modifier = "set" | "add" | "subtract";
Expand All @@ -74,8 +87,12 @@ export type Num = number | { epdOf: string; plus: number } | { addrOf: string; p
*/
export type Value = Num | { label: string } | { epd: Dword };

/** The dword compared with `value` (read from 0 up); with `mask`, the dword's bits of the mask (`value` as it is). */
export interface Cond { dword: Dword; compare: Compare; value: number; mask?: number }
/**
* The dword compared with `value` (read from 0 up); with `mask`, the dword's bits of the mask (`value` as it is).
* `game`: instead, a condition of the game's as the script wrote it (a map of our own: TrigScript 4, step 5 of the way
* off eudplib) — its record laid out as it is, the dword not read.
*/
export interface Cond { dword: Dword; compare: Compare; value: number; mask?: number; game?: ConditionRecord }

/**
* `value` written into the dword, added to it or taken from it; with `mask`, only the mask's bits change. An add wraps;
Expand All @@ -93,7 +110,13 @@ export interface Cond { dword: Dword; compare: Compare; value: number; mask?: nu
* game's own random numbers, the same on every player's machine. A map of our own reads the switch where the game keeps
* it (`SWITCHES`).
*/
export interface Act { dword: Dword; modifier: Modifier; value: Value; mask?: number; at?: At; display?: string; show?: string; randomize?: number }
/**
* `record`: instead of a write, an action of the game's as the script wrote it (a map of our own), laid out as it is but
* for its text and its sound file, which are the script's strings (`strings`: a record's `text` / `wav` of `k > 0` is the
* script's `k`-th) and are put in the map's strings when the map is made.
*/
/** `textOf`: an action of the game's whose text is a printed text's template (`Graph.templates`), filled before it. */
export interface Act { dword: Dword; modifier: Modifier; value: Value; mask?: number; at?: At; display?: string; show?: string; randomize?: number; record?: ActionRecord; strings?: { text?: ScriptString; wav?: ScriptString }; textOf?: string }

/** Where the game keeps its 256 switches: a bit each, 32 to a dword. */
export const SWITCHES = 0x58dc40;
Expand Down
44 changes: 40 additions & 4 deletions compiler/backend/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
import { END, TRIGGER_BYTES, type Act, type Cond, type Dword, type Graph, type Value } from "./graph";
import { GAME_TICK, WAKE_AT, WAKE_NEXT, WAKE_TICK } from "./emit";
import type { ScriptString } from "../runtime";

export const STRINGS = 0x191943c8;
const DEATHS = 0x58a364;
Expand Down Expand Up @@ -53,7 +54,7 @@ export interface LayoutInput {
strings: number;
/** The program's player, 0-based. */
owner: number;
/** A text's index in the map's string table (1-based), for Display Text. */
/** A text's index in the map's string table (1-based), for Display Text and an action's text or sound file. */
text: (t: string) => number;
/** The cells of the lists the graph reads where they are, by array id. */
arrays: Map<string, number[]>;
Expand Down Expand Up @@ -188,8 +189,9 @@ export function layout(input: LayoutInput): OwnMap {
if (t === undefined) throw new Error(`no trigger ${d.at}`);
const f = d.field;
if (f === "next") return t + 4;
if (f === "current") return t + 2404;
if ("cond" in f) return t + 8 + 20 * f.cond + (f.part === "player" ? 4 : f.part === "mask" ? 0 : 8);
return t + 328 + 32 * f.act + { value: 20, dest: 16, modifier: 24, mask: 0 }[f.part];
return t + 328 + 32 * f.act + { value: 20, dest: 16, modifier: 24, mask: 0, text: 4, wav: 8, time: 12, flags: 28 }[f.part];
};
const address = (d: Dword): number => {
if ("game" in d) return d.game;
Expand Down Expand Up @@ -240,8 +242,11 @@ export function layout(input: LayoutInput): OwnMap {
// (Every address first: the cells are allocated as the triggers name them, so the size is known after.)
const encoded = triggers.map((t) => ({
t,
conds: t.conds.map((c) => ({ c, player: epd(address(c.dword)) })),
acts: t.acts.map((a) => ({ a, player: a.display !== undefined || a.show !== undefined || a.randomize !== undefined ? 0 : epd(address(a.dword)), number: a.display !== undefined || a.show !== undefined || a.randomize !== undefined ? 0 : value(a.value) })),
conds: t.conds.map((c) => ({ c, player: c.game ? 0 : epd(address(c.dword)) })),
acts: t.acts.map((a) => {
const none = a.display !== undefined || a.show !== undefined || a.randomize !== undefined || a.record !== undefined;
return { a, player: none ? 0 : epd(address(a.dword)), number: none ? 0 : value(a.value) };
}),
}));
const size = next - base;
const out = new Uint8Array(pad + size);
Expand All @@ -254,6 +259,17 @@ export function layout(input: LayoutInput): OwnMap {
if (conds.length > 16 || acts.length > 64) throw new Error(`${t.label}: too many conditions or actions`);
conds.forEach(({ c, player }, k) => {
const o = n + 8 + 20 * k;
if (c.game) {
// A condition of the game's, as the script wrote it.
const r = c.game;
w32(o, r.location);
w32(o + 4, r.player);
w32(o + 8, r.amount);
dv.setUint16(pad + o + 12 - base, r.unitId, true);
for (const [at, b] of [[14, r.comparison], [15, r.type], [16, r.resource], [17, r.flags]]) dv.setUint8(pad + o + at - base, b);
dv.setUint16(pad + o + 18 - base, r.mask, true);
return;
}
w32(o, c.mask ?? 0);
w32(o + 4, player);
w32(o + 8, c.value);
Expand All @@ -263,6 +279,26 @@ export function layout(input: LayoutInput): OwnMap {
});
acts.forEach(({ a, player, number }, k) => {
const o = n + 328 + 32 * k;
if (a.record) {
// An action of the game's, as the script wrote it: its text and its sound file put among the map's strings.
const r = a.record;
const str = (s: ScriptString | undefined, k: number) => {
if (!k) return 0;
if (!s) throw new Error(`${t.label}: an action's string ${k} with no text given for it`);
return "index" in s ? s.index : input.text(s.text);
};
w32(o, r.location);
if (a.textOf !== undefined && !input.template) throw new Error(`an action's text (${a.textOf}) with no string to show it by`);
w32(o + 4, a.textOf !== undefined ? input.template!(a.textOf) : str(a.strings?.text, r.text));
w32(o + 8, str(a.strings?.wav, r.wav));
w32(o + 12, r.time);
w32(o + 16, r.player);
w32(o + 20, r.target);
dv.setUint16(pad + o + 24 - base, r.unitId, true);
for (const [at, b] of [[26, r.type], [27, r.modifier], [28, r.flags], [29, r.padding]]) dv.setUint8(pad + o + at - base, b);
dv.setUint16(pad + o + 30 - base, r.mask, true);
return;
}
if (a.randomize !== undefined) {
// Set Switch (13), its switch in the record's number, Randomize (11).
w32(o + 20, a.randomize);
Expand Down
8 changes: 6 additions & 2 deletions compiler/backend/own.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type { ArrayDecl, FuncDecl, Program, VarDecl } from "../ir";
import { emitBody } from "./emit";
import type { Graph } from "./graph";

export function emitOwn(r: Pick<CompileResult, "ir" | "triggers">): { program: Program; graph: Graph } | string {
export function emitOwn(r: Pick<CompileResult, "ir" | "triggers" | "strings">): { program: Program; graph: Graph } | string {
if (r.ir.length !== 1) return `a map of our own has one program; this script has ${r.ir.length}`;
const program = r.ir[0];
if (program.perPlayer) return "a map of our own has no per-player program yet";
Expand All @@ -29,5 +29,9 @@ export function emitOwn(r: Pick<CompileResult, "ir" | "triggers">): { program: P
const functions = new Map<string, FuncDecl>((program.functions ?? []).map((f) => [f.id, f]));
const arrays = new Map<string, ArrayDecl>((program.arrays ?? []).map((a) => [a.id, a]));
const graph = emitBody(program.body, decls, "own", functions, arrays, undefined, program.owner);
return typeof graph === "string" ? graph : { program, graph };
if (typeof graph === "string") return graph;
// (An action of the game's names its text and its sound file by the script's strings: given here, for the layout.)
const of = (k: number) => (k ? r.strings[k - 1] : undefined);
for (const t of graph.triggers) for (const a of t.acts) if (a.record && (a.record.text || a.record.wav)) a.strings = { text: of(a.record.text), wav: of(a.record.wav) };
return { program, graph };
}
Loading
Loading