-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
move to mutable state, dirty-checking and renderloop
This commit makes the code quite simpler removing all the callback passing and uses a mutable opaque type as the state. Edit operations on the state mark it as dirty, causing a redraw during a rAF render loop. On my device this results in noticeably better response times by the canvas when drawing new lines.
- Loading branch information
Showing
8 changed files
with
218 additions
and
174 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,43 +1,84 @@ | ||
import { Newtype, _iso } from './newtype'; | ||
|
||
export type Point = { | ||
x: number; | ||
y: number; | ||
readonly x: number; | ||
readonly y: number; | ||
}; | ||
|
||
type Priv = Array<Point[]>; | ||
type State = Priv & { _T: 'State' }; | ||
type S = { | ||
lines: Array<Point[]>; | ||
drawing: Point[]; | ||
dirty: boolean; | ||
}; | ||
|
||
const store = window.localStorage || window.sessionStorage; | ||
export interface State extends Newtype<{ readonly State: unique symbol }, S> {} | ||
|
||
export const load = (): State => JSON.parse(store.getItem('state') || '[]'); | ||
const iso = _iso<State>(); | ||
const db = window.localStorage || window.sessionStorage; | ||
const dump = JSON.stringify; | ||
const parse = JSON.parse; | ||
|
||
export const save = (x: State): void => { | ||
store.setItem('state', JSON.stringify(x)); | ||
}; | ||
function save(x: S): void { | ||
db.setItem('state', dump(x)); | ||
} | ||
|
||
export const empty = (): State => JSON.parse('[]'); | ||
export function load(): State { | ||
const item = db.getItem('state'); | ||
return item ? iso.wrap({ ...parse(item), dirty: true }) : empty(); | ||
} | ||
|
||
const cast = (x: Priv): State => x as State; | ||
export function empty(): State { | ||
const result: S = { | ||
lines: [], | ||
drawing: [], | ||
dirty: true, | ||
}; | ||
|
||
export const map = (state: State, cb: (p: Point) => Point): State => | ||
cast(state.map(l => l.map(z => cb(z)))); | ||
save(result); | ||
return iso.wrap(result); | ||
} | ||
|
||
export const iterator = (state: State) => (cb: (p: Point[]) => void): void => { | ||
state.forEach(cb); | ||
}; | ||
export function map(s: State, cb: (x: Point) => Point): State { | ||
const state = iso.unwrap(s); | ||
const dup: S = parse(dump(state)); | ||
dup.lines = dup.lines.map(l => l.map(cb)); | ||
return iso.wrap(dup); | ||
} | ||
|
||
export const handleAdd = (state: State) => (l: Point[]) => { | ||
state.push(l); | ||
export function undo(s: State): void { | ||
const state = iso.unwrap(s); | ||
state.lines.splice(-1, 1); | ||
state.dirty = true; | ||
save(state); | ||
}; | ||
} | ||
|
||
export const handleUndo = (state: State) => () => { | ||
state.splice(-1, 1); | ||
export function clear(s: State): void { | ||
const state = iso.unwrap(s); | ||
state.lines.splice(0, state.lines.length); | ||
state.dirty = true; | ||
save(state); | ||
return iterator(state); | ||
}; | ||
} | ||
|
||
export const handleClear = (state: State) => () => { | ||
state.splice(0, state.length); | ||
export function addDrawingPoint(s: State, p: Point): void { | ||
const state = iso.unwrap(s); | ||
state.drawing.push(p); | ||
state.dirty = true; | ||
save(state); | ||
return iterator(state); | ||
}; | ||
} | ||
|
||
export function addLastDrawingPoing(s: State, p: Point): void { | ||
const state = iso.unwrap(s); | ||
state.drawing.push(p); | ||
state.lines.push(state.drawing); | ||
state.drawing = []; | ||
state.dirty = true; | ||
save(state); | ||
} | ||
|
||
export function willdisplay(s: State, cb: (lines: Array<Point[]>) => boolean) { | ||
const state = iso.unwrap(s); | ||
if (state.dirty) { | ||
const successful = cb([...state.lines, state.drawing]); | ||
state.dirty = !successful; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// barebones version of https://github.com/gcanti/newtype-ts | ||
|
||
export interface Newtype<URI, A> { | ||
_URI: URI; | ||
_A: A; | ||
} | ||
|
||
export interface Iso<S, A> { | ||
unwrap: (s: S) => A; | ||
wrap: (a: A) => S; | ||
} | ||
|
||
function identity<T>(x: T): T { | ||
return x; | ||
} | ||
|
||
export const _iso = <S extends Newtype<any, any> = never>(): Iso< | ||
S, | ||
S['_A'] | ||
> => ({ | ||
unwrap: identity, | ||
wrap: identity, | ||
}); |
Oops, something went wrong.