Skip to content

Instantly share code, notes, and snippets.

@thomcc
Created April 25, 2018 23:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomcc/a960b98f8a374d9f1d58f0cfe26cacbd to your computer and use it in GitHub Desktop.
Save thomcc/a960b98f8a374d9f1d58f0cfe26cacbd to your computer and use it in GitHub Desktop.
// Coord is a way of storing a 2d integer vector (e.g. vec2i or similar) as a
// value type by packing it into a number. It will do what you want in a Map or
// Set, you can compare them for equality, and it should be free of alloctions
// (at least in Spidermonkey and V8, but probably anywhere else too).
//
// The downside is that it's a pain to debug, use, and it has ugly syntax. It's
// type safe (in typescript) tho.
//
// Each component is stored as a 15 bit (signed, 2's complement) integer. These
// must be between COORD_MIN and COORD_MAX, inclusive. It's a bad choice if
// there's any chance the values will not fit.
//
// layout: 0b00YYYYYYYYYYYYYYYXXXXXXXXXXXXXXX
//
// It uses 15 bits and not 16, as using all 32 bits will overfill SMI_MAX in V8,
// so we only use 30[0], and your number will end up allocated on the heap (it
// generally will optimize this away, but, well, it seems bad to rely on that
// for something that is explicitly trading ease of use for this.
//
// (Strictly speaking, we could have a pair of one 15 bit number and one 16 bit
// number, but that sounds pointless and annoying. It's a little tempting to try
// to store a bit of padding data between the two components for better sanity
// checking, and so a few helpers could be written more efficiently e.g. add by
// just adding numbers directly, then checking for overflow and asserting or
// w/e).
//
// All that said, This is still more expensive to use than just passing around 2
// numbers, and it is doubtful that the compiler can optimize away the overhead.
// That said, the reduced number of allocations should reduce GC pressure, the
// fact that the coord's data can be stored on the stack which should improve
// cache locality, so overall it will probably perform more predictably (and
// likely better) than using an `{x: number, y: number}` object or similar. It's
// a bit of a pain in the ass though.
//
// Note: It would be easy to write a version of this for non-integer coordinates
// by using fixed point numbers for x and y. I *almost* did this too but talked
// myself out of it.
// Magic type that isn't convertable to anything else.
interface Coord extends Number {
// Dummy propery to prevent Coord having implicit conversions between Coords
// and empty objects.
_coordBrand: number;
}
const COORD_MIN = -0x4000;
const COORD_MAX = 0x3fff;
function rawMkCoord(x: number, y: number): Coord {
return (((y & 0x7fff) << 15) | (x & 0x7fff)) as any;
}
// Asserts if x and y aren't in the right range
function makeCoord(x: number, y: number) {
if (DEBUG) {
Assert.int2(x, y);
Assert.betweenI(x, COORD_MIN, COORD_MAX);
Assert.betweenI(y, COORD_MIN, COORD_MAX);
}
return rawMakeCoord(x, y)
}
// rounds and clamps to ensure x and y are in the right range
function coord(x: number, y: number) {
x = math.clamp(Math.round(x), COORD_MIN, COORD_MAX);
y = math.clamp(Math.round(y), COORD_MIN, COORD_MAX);
return rawMakeCoord(x, y)
}
// Assert the coord is not obviously invalid and cast.
function coordToNumber(c: Coord) {
if (DEBUG) Assert.eq(c, c & 0x3fffffff);
return (c as any) as number;
}
function coordX(c: Coord): number { return (coordToNumber(c) << 17) >> 17; }
function coordY(c: Coord): number { return (coordToNumber(c) << 2) >> 17; }
function coordWithX(c: Coord, x: number): Coord {
const n = coordToNumber(c);
return (((n & 0x3fff8000) | (x & 0x7fff)) as any) as Coord;
}
function coordWithY(c: Coord, y: number): Coord {
const n = coordToNumber(c);
return (((n & 0x7fff) | ((y & 0x7fff) << 15)) as any) as Coord;
}
function coordToString(c: Coord) {
return `Coord(${coordX(c)}, ${coordY(c)})`
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment