Snippets

Patrick Logan One Way Mark and Sweep Constraints

Created by Patrick Logan last modified
package cell

var (
	hooks = make(map[*formula]Hook)
)

// Type Context is used to create each kind of cell. Formulas that
// depend on cells and other formulas must all be created with the
// same context. A context and its cells are not concurrent. They
// should be used within a single thread.
type Context interface {
	// Function NewBox returns a simple cell that always ignores any
	// dependents.
	NewBox(value interface{}) Cell
	NewCell(value interface{}) Cell
	NewFormula(f Formula) Cell
	NewSettableFormula(value interface{}, f Formula) Cell
	NewDaemon(d Daemon) Cell
	SetNullifyHook(c Cell, h Hook) bool
	NullifyHook(c Cell) (Hook, bool)
}

type Hook func(formula Cell)

// Type Cell is used to set and get a value such that other cells
// (formulas) can depend on being recalculated when the depended-upon
// cell value is changed. A dependency is a one-directional dataflow,
// much like a spreadsheet formula. But see NewBox, which creates a
// cell that does not notify dependents.
type Cell interface {
	// Function Value returns the value of the receiver, calculating
	// based on dependencies as needed.
	Value() interface{}

	// Function SetValue sets the value of the receiver, outdating
	// dependents as needed.
	SetValue(value interface{})

	// Function Context returns the context of the receiver.
	Context() Context
}

// Type Daemon is essentially a "forwarder" to a custom implementation
// of a Cell. There is no expectation that dependencies will be
// updated on changes. Any of the methods may have side-effects with
// the outside world.
type Daemon interface {
	Cell
	SetContext(c Context)
}

type Formula func() interface{}

func NewContext() Context {
	return &context{}
}

type context struct {
	stack []*formula
}

type box struct {
	v interface{}
	c *context
}

type cell struct {
	v interface{}
	tick int
	outdated bool
	dependents []*dependency
	c *context
}

type formula struct {
	*cell
	f Formula
}

type dependency struct {
  f *formula
  tick int
}

func (c *cell) nullify() {
	c.outdated = true
	ds := []*dependency{}
	for _, d := range c.dependents {
		if !d.isStale() && !d.isOutdated() {
			d.nullify()    // Nullify dependents of dependents of...
			ds = append(ds, d)
		}
	}
	c.dependents = ds
}

func (d *dependency) isStale() bool {
	return d.tick < d.f.tick
}

func (d *dependency) isOutdated() bool {
	return d.f.outdated
}

func (d *dependency) nullify() {
	d.f.nullify()
	if h, ok := hooks[d.f]; ok {
		h(d.f)
	}
}

func (c *context) push(f *formula) {
	c.stack = append([]*formula{f}, c.stack...)
}

func (c *context) pop() {
	c.stack = c.stack[1:]
}

func (f *formula) refresh() {
	if f.outdated {
		f.c.push(f)
		defer f.c.pop()
		f.outdated = false
		f.v = f.f()
		f.tick++
	}
}

func findFormula(dependencies []*dependency, f *formula) *dependency {
	for _, d := range dependencies {
		if d.f == f {
			return d
		}
	}
	return nil
}

func (c *cell) updateDependent(f *formula) {
	d0 := findFormula(c.dependents, f)
	if d0 == nil {
		d1 := &dependency{f, f.tick + 1}
		c.dependents = append(c.dependents, d1)
	} else {
		d0.tick = f.tick + 1;
	}
}

func (c *cell) value(f *formula) interface{} {
	if len(c.c.stack) > 0 {
		c.updateDependent(c.c.stack[0])
	}
	if f != nil {
		f.refresh()
	}
	return c.v
}

func (c *context) NewBox(value interface{}) Cell {
	return &box{value, c}
}

func (c *context) NewCell(value interface{}) Cell {
	return &cell{value, 0, false, []*dependency{}, c}
}

func (c *context) NewFormula(f Formula) Cell {
	cell := &cell{nil, 0, true, []*dependency{}, c}    // Initially value is outdated so the function will compute it.
	return &formula{cell, f}
}

type settableFormulaDaemon struct {
	c *cell
	f *formula
	currentFormula *formula
}

func (d *settableFormulaDaemon) Context() Context {
	return d.c.c
}

func (d *settableFormulaDaemon) SetContext(c Context) {
	// TODO: assert that c is the same as d.c.c and d.f.c
}

func (d *settableFormulaDaemon) Value() interface{} {
	if d.currentFormula == nil {
		d.currentFormula = d.f
		return d.c.Value()
	} else {
		return d.f.Value()
	}
}

func (d *settableFormulaDaemon) SetValue(value interface{}) {
	d.c.SetValue(value)
	d.currentFormula = nil
}

func (c *context) NewSettableFormula(value interface{}, f Formula) Cell {
	return &settableFormulaDaemon{ c.NewCell(value).(*cell), c.NewFormula(f).(*formula), nil }
}

func (c *context) NewDaemon(d Daemon) Cell {
	d.SetContext(c)
	return d
}

// Method SetNullifyHook associates the Hook with the Cell if the Cell
// is a formula. The result is true if the hook was set, false if the
// Cell is not a formula.
func (c *context) SetNullifyHook(cell Cell, h Hook) (ok bool) {
	ok = false
	if f, fOk := cell.(*formula); fOk {
		hooks[f] = h    // Currently just zero or one hook per formula
		ok = true
	}
	return
}

func (c *context) NullifyHook(cell Cell) (h Hook, ok bool) {
	ok = false
	if f, fOk := cell.(*formula); fOk {
		h, ok = hooks[f]
	}
	return
}

func (b *box) Value() interface{} {
	return b.v
}

func (b *box) SetValue(value interface{}) {
	b.v = value
}

func (b *box) Context() Context {
	return b.c
}

func (c *cell) Value() interface{} {
	return c.value(nil)
}

func (c *cell) SetValue(value interface{}) {
	c.nullify()
	c.v = value
}

func (c *cell) Context() Context {
	return c.c
}

func (f *formula) Value() interface{} {
	return f.value(f)
}

func (f *formula) SetValue(value interface{}) {
	// NOOP -- see "settable formulas" and "daemons"
}

func (f *formula) Context() Context {
	return f.c
}

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.