Skip to content

Commit

Permalink
Allow non-string keys for tilemaps
Browse files Browse the repository at this point in the history
Now that each backend can specify its own types for allowable
characters, the tile and tile-gl layouts can use number or symbol keys
for each tile, rather than requiring that each "character" be a string.
  • Loading branch information
dmchurch committed Mar 27, 2024
1 parent 12c4bca commit 4f0c770
Show file tree
Hide file tree
Showing 7 changed files with 42 additions and 38 deletions.
9 changes: 6 additions & 3 deletions src/display/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,11 @@ export interface Display<TLayout extends LayoutType = LayoutType, TChar = Layout
}

type LayoutFor<TOptions extends DisplayOptions> = TOptions extends {layout: infer L} ? NonNullable<L> : typeof DisplayImpl["DEFAULT_LAYOUT"];
type BackendFor<TOptions extends DisplayOptions> = LayoutBackend<LayoutFor<TOptions>>;
type TCharFromOptions<TOptions extends DisplayOptions> = BackendChars<BackendFor<TOptions>>;
type BackendFor<TOptions extends DisplayOptions> = LayoutBackend<LayoutFor<TOptions>, TOptions>;
type TCharFromOptions<TOptions extends DisplayOptions> =
TOptions extends {tileMap: Record<infer TKey, any>} ? TKey[]
: TOptions extends {tileMap: readonly any[]} ? number[]
: BackendChars<BackendFor<TOptions>>;
type TFGColorFromOptions<TOptions extends DisplayOptions> = BackendFGColor<BackendFor<TOptions>>;
type TBGColorFromOptions<TOptions extends DisplayOptions> = BackendBGColor<BackendFor<TOptions>>;

Expand All @@ -384,6 +387,6 @@ export interface DisplayConstructor {
}

export const Display = class Display<TLayout extends LayoutType, TChar, TFGColor, TBGColor> extends DisplayImpl<TLayout, TChar, TFGColor, TBGColor> {
} as any;
} as unknown as DisplayConstructor;

export default Display;
2 changes: 1 addition & 1 deletion src/display/hex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { TextDisplayOptions, DefaultsFor, DisplayOptions } from "./types.js";
import { mod } from "../util.js";

declare module "./types.js" {
interface LayoutTypeBackendMap {
interface LayoutTypeBackendMap<TOptions extends DisplayOptions> {
hex: Hex;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/display/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Canvas, { CanvasDisplayData } from "./canvas.js";
import { DefaultsFor, DisplayOptions, TextDisplayOptions } from "./types.js";

declare module "./types.js" {
interface LayoutTypeBackendMap {
interface LayoutTypeBackendMap<TOptions extends DisplayOptions> {
rect: Rect;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/display/term.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { BaseDisplayOptions, DisplayData, DisplayOptions } from "./types.js";
import * as Color from "../color.js";

declare module "./types.js" {
interface LayoutTypeBackendMap {
interface LayoutTypeBackendMap<TOptions extends DisplayOptions> {
term: Term;
}
}
Expand Down
24 changes: 12 additions & 12 deletions src/display/tile-gl.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import Backend from "./backend.js";
import { TileDisplayOptions, DisplayData, DefaultsFor, DisplayOptions } from "./types.js";
import { TileDisplayOptions, DisplayData, TileMapKey, DefaultsFor, DisplayOptions } from "./types.js";
import * as Color from "../color.js";

declare module "./types.js" {
interface LayoutTypeBackendMap {
"tile-gl": TileGL;
interface LayoutTypeBackendMap<TOptions extends DisplayOptions> {
"tile-gl": TileGL<TOptions extends {tileMap: Record<infer TKey, any>} ? TKey : string>;
}
}

export interface TileGLOptions extends TileDisplayOptions {
export interface TileGLOptions<TChar extends TileMapKey = string> extends TileDisplayOptions<TChar> {
layout: "tile-gl";
}
export interface TileGLData extends DisplayData<string[], string[], string[]> {
export interface TileGLData<TChar extends TileMapKey> extends DisplayData<TChar[], string[], string[]> {
}

/**
* @class Tile backend
* @private
*/
export default class TileGL extends Backend<TileGLOptions, string[], string[], string[], TileGLData> {
export default class TileGL<TChar extends TileMapKey = string> extends Backend<TileGLOptions<TChar>, TChar[], string[], string[], TileGLData<TChar>> {
_gl!: WebGLRenderingContext;
_program!: WebGLProgram;
_uniforms: {[key:string]: WebGLUniformLocation | null};
Expand All @@ -29,7 +29,7 @@ export default class TileGL extends Backend<TileGLOptions, string[], string[], s
tileWidth: 32,
tileHeight: 32,
tileColorize: false,
} satisfies DefaultsFor<TileGLOptions>;
} satisfies DefaultsFor<TileGLOptions<never>>;
}
static isSupported() {
return !!document.createElement("canvas").getContext("webgl2", {preserveDrawingBuffer:true});
Expand All @@ -52,11 +52,11 @@ export default class TileGL extends Backend<TileGLOptions, string[], string[], s
schedule(cb: () => void) { requestAnimationFrame(cb); }
getContainer() { return this._gl.canvas as HTMLCanvasElement; }

checkOptions(options: DisplayOptions): options is TileGLOptions {
checkOptions(options: DisplayOptions): options is TileGLOptions<TChar> {
return options.layout === "tile-gl";
}

setOptions(opts: TileGLOptions) {
setOptions(opts: TileGLOptions<TChar>) {
let needsRepaint = super.setOptions(opts);

this._updateSize();
Expand All @@ -71,14 +71,14 @@ export default class TileGL extends Backend<TileGLOptions, string[], string[], s
return needsRepaint;
}

protected defaultedOptions(options: TileGLOptions): Required<TileGLOptions> {
protected defaultedOptions(options: TileGLOptions<TChar>): Required<TileGLOptions<TChar>> {
return {
...this.DEFAULTS,
...options,
}
}

draw(data: TileGLData, clearBefore: boolean) {
draw(data: TileGLData<TChar>, clearBefore: boolean) {
const gl = this._gl;
const opts = this._options;
const {x, y, chars, fgs, bgs} = data;
Expand All @@ -101,7 +101,7 @@ export default class TileGL extends Backend<TileGLOptions, string[], string[], s

for (let i=0;i<chars.length;i++) {
let tile = this._options.tileMap[chars[i]];
if (!tile) { throw new Error(`Char "${chars[i]}" not found in tileMap`); }
if (!tile) { throw new Error(`Char "${String(chars[i])}" not found in tileMap`); }

gl.uniform1f(this._uniforms["colorize"], opts.tileColorize ? 1 : 0);
gl.uniform2fv(this._uniforms["tilesetPosAbs"], tile);
Expand Down
22 changes: 11 additions & 11 deletions src/display/tile.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { BaseCanvas } from "./canvas.js";
import { TileDisplayOptions, DisplayData, UnknownBackend, DefaultsFor, DisplayOptions } from "./types.js";
import { TileDisplayOptions, DisplayData, UnknownBackend, TileMapKey, DefaultsFor, DisplayOptions } from "./types.js";

declare module "./types.js" {
interface LayoutTypeBackendMap {
"tile": Tile;
interface LayoutTypeBackendMap<TOptions extends DisplayOptions> {
"tile": Tile<TOptions extends {tileMap: Record<infer TKey, any>} ? TKey : string>;
}
}

export interface TileOptions extends TileDisplayOptions {
export interface TileOptions<TChar extends TileMapKey = string> extends TileDisplayOptions<TChar> {
layout: "tile";
}
export interface TileData extends DisplayData<string[], string[], string[]> {
export interface TileData<TChar extends TileMapKey> extends DisplayData<TChar[], string[], string[]> {
}

/**
* @class Tile backend
* @private
*/
export default class Tile extends BaseCanvas<TileOptions, TileData, string[], string[], string[]> {
export default class Tile<TChar extends TileMapKey = string> extends BaseCanvas<TileOptions<TChar>, TileData<TChar>, TChar[], string[], string[]> {
_colorCanvas: HTMLCanvasElement;

protected get DEFAULTS() {
Expand All @@ -26,26 +26,26 @@ export default class Tile extends BaseCanvas<TileOptions, TileData, string[], st
tileWidth: 32,
tileHeight: 32,
tileColorize: false,
} satisfies DefaultsFor<TileOptions>;
} satisfies DefaultsFor<TileOptions<never>>;
}

constructor(oldBackend?: UnknownBackend) {
super(oldBackend);
this._colorCanvas = oldBackend instanceof Tile ? oldBackend._colorCanvas : document.createElement("canvas");
}

checkOptions(options: DisplayOptions): options is TileOptions {
checkOptions(options: DisplayOptions): options is TileOptions<TChar> {
return options.layout === "tile";
}

protected defaultedOptions(options: TileOptions): Required<TileOptions> {
protected defaultedOptions(options: TileOptions<TChar>): Required<TileOptions<TChar>> {
return {
...this.DEFAULTS,
...options,
}
}

draw(data: TileData, clearBefore: boolean) {
draw(data: TileData<TChar>, clearBefore: boolean) {
const {x, y, chars, fgs, bgs} = data;

let tileWidth = this._options.tileWidth;
Expand All @@ -70,7 +70,7 @@ export default class Tile extends BaseCanvas<TileOptions, TileData, string[], st

for (let i=0;i<chars.length;i++) {
let tile = this._options.tileMap[chars[i]];
if (!tile) { throw new Error(`Char "${chars[i]}" not found in tileMap`); }
if (!tile) { throw new Error(`Char "${String(chars[i])}" not found in tileMap`); }

if (this._options.tileColorize) { // apply colorization
let canvas = this._colorCanvas;
Expand Down
19 changes: 10 additions & 9 deletions src/display/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export interface LayoutTypeBackendMap {
export interface LayoutTypeBackendMap<TOptions extends DisplayOptions> {
// Entries in this map are added via declaration merging, see the top of e.g. rect.ts
}

export type LayoutType = {[TLayout in keyof LayoutTypeBackendMap]: TLayout}[keyof LayoutTypeBackendMap];
export type LayoutType = {[TLayout in keyof LayoutTypeBackendMap<any>]: TLayout}[keyof LayoutTypeBackendMap<any>];
export type AnyBackend = IDisplayBackend<any, any>;
export type UnknownBackend = IDisplayBackend<BaseDisplayOptions, DisplayData>;

Expand All @@ -11,11 +11,11 @@ export type BackendChars<TBackend extends AnyBackend> = TBackend extends IDispla
export type BackendFGColor<TBackend extends AnyBackend> = TBackend extends IDisplayBackend<any, infer TData> ? TData["fg"] : never;
export type BackendBGColor<TBackend extends AnyBackend> = TBackend extends IDisplayBackend<any, infer TData> ? TData["bg"] : never;

export type LayoutBackend<TLayout extends LayoutType> = LayoutTypeBackendMap[TLayout];
export type LayoutOptions<TLayout extends LayoutType> = BackendOptions<LayoutBackend<TLayout>>
export type LayoutChars<TLayout extends LayoutType> = BackendChars<LayoutBackend<TLayout>>
export type LayoutFGColor<TLayout extends LayoutType> = BackendFGColor<LayoutBackend<TLayout>>
export type LayoutBGColor<TLayout extends LayoutType> = BackendBGColor<LayoutBackend<TLayout>>
export type LayoutBackend<TLayout extends LayoutType, TOptions extends DisplayOptions> = LayoutTypeBackendMap<TOptions>[TLayout];
export type LayoutOptions<TLayout extends LayoutType> = BackendOptions<LayoutBackend<TLayout, any>>
export type LayoutChars<TLayout extends LayoutType, TOptions extends DisplayOptions = {}> = BackendChars<LayoutBackend<TLayout, TOptions>>
export type LayoutFGColor<TLayout extends LayoutType, TOptions extends DisplayOptions = {}> = BackendFGColor<LayoutBackend<TLayout, TOptions>>
export type LayoutBGColor<TLayout extends LayoutType, TOptions extends DisplayOptions = {}> = BackendBGColor<LayoutBackend<TLayout, TOptions>>

export interface BaseDisplayOptions {
width?: number;
Expand All @@ -33,10 +33,11 @@ export interface TextDisplayOptions extends BaseDisplayOptions {
fontStyle?: string;
}

export interface TileDisplayOptions extends BaseDisplayOptions {
export type TileMapKey = string | number | symbol;
export interface TileDisplayOptions<TChar extends TileMapKey = string> extends BaseDisplayOptions {
tileWidth?: number;
tileHeight?: number;
tileMap: { [key: string]: [number, number] };
tileMap: { [K in TChar]: [number, number] };
tileSet: null | HTMLCanvasElement | HTMLImageElement | HTMLVideoElement | ImageBitmap;
tileColorize?: boolean;
}
Expand Down

0 comments on commit 4f0c770

Please sign in to comment.