+/*----- Hooks -------------------------------------------------------------*/
+
+/* This is a really simple publisher/subscriber system. The only slight
+ * tweak -- and the reason I'm not just using the Scala machinery -- is that
+ * being attached to a hook doesn't prevent the client from being garbage
+ * collected.
+ */
+
+trait BaseHookClient[E] {
+ /* The minimal requirements for a hook client. Honestly you should be
+ * using `HookClient' instead.
+ */
+
+ type H = Hook[E]; // the type of hook we attach to
+ def hook(hk: H, evt: E); // called with events from the hook
+}
+
+trait HookClient[E] extends BaseHookClient[E] {
+ /* The properly cooked hook client. This keeps track of which hooks we're
+ * attached to so we can release them all easily.
+ */
+
+ private val hooks = new HashSet[H];
+ protected def attachHook(hk: H) { hk.addHookClient(this); hooks += hk; }
+ protected def detachHook(hk: H) { hk.rmHookClient(this); hooks -= hk; }
+ protected def detachAllHooks()
+ { for (hk <- hooks) hk.rmHookClient(this); hooks.clear(); }
+}
+
+trait Hook[E] {
+ type C = BaseHookClient[E];
+ private val clients = new WeakHashMap[C, Unit];
+ def addHookClient(c: C) { clients(c) = (); }
+ def rmHookClient(c: C) { clients -= c; }
+ protected def callHook(evt: E)
+ { for (c <- clients.keys) c.hook(this, evt); }
+}
+
+/*----- Fluid variables ---------------------------------------------------*/
+
+object BaseFluid {
+ /* The multi-fluid `let' form is defined here so that it can access the
+ * `capture' method of individual fluids, but users should use the
+ * package-level veneer.
+ */
+
+ private[tripe] def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)
+ (body: => U): U = {
+ /* See the package-level `let' for details. */
+ val binds = for ((f, _) <- fxs) yield f.capture;
+ try { for ((f, x) <- fxs) f.v = x; body }
+ finally { for (b <- binds) b.restore(); }
+ }
+}
+def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)(body: => U): U = {
+ /* let(F -> X, ...) { BODY }
+ *
+ * Evaluate BODY in a dynamic context where each fluid F is bound to the
+ * corresponding value X.
+ */
+
+ BaseFluid.let(fxs: _*)(body);
+}
+
+trait BaseFluid[T] {
+ /* The basic fluid protocol. */
+
+ override def toString(): String =
+ f"${getClass.getName}%s@${hashCode}%x($v%s)";
+
+ protected trait Binding {
+ /* A captured binding which can be restored later. Implementing this is
+ * a subclass responsibility.
+ */
+
+ def restore();
+ /* Restore the fluid's state to the state captured here. */
+ }
+
+ /* Fetch and modify the current binding. */
+ def v: T;
+ def v_=(x: T);
+
+ protected def capture: Binding;
+ /* Capture and the current state of the fluid. */
+
+ def let[U](x: T)(body: => U): U = {
+ /* let(X) { BODY }
+ *
+ * Evaluate BODY in a dynamic context where the fluid is bound to the
+ * value X.
+ */
+
+ val b = capture;
+ try { v = x; body } finally { b.restore(); }
+ }
+}
+
+class SharedFluid[T](init: T) extends BaseFluid[T] {
+ /* A simple global fluid. It's probably a mistake to try to access a
+ * `SharedFluid' from multiple threads without serious synchronization.
+ */
+
+ var v: T = init;
+ private class Binding(old: T) extends super.Binding
+ { def restore() { v = old; } }
+ protected def capture: super.Binding = new Binding(v);
+}
+
+class ThreadFluid[T](init: T) extends BaseFluid[T] {
+ /* A thread-aware fluid. The top-level binding is truly global, shared by
+ * all threads, but `let'-bindings are thread-local.
+ */
+
+ private[this] var global: T = init;
+ private[this] var bound: ThreadLocal[Option[T]] = new ThreadLocal;
+ bound.set(None);
+
+ def v: T = bound.get match { case None => global; case Some(x) => x; };
+ def v_=(x: T) { bound.get match {
+ case None => global = x;
+ case _ => bound.set(Some(x));
+ } }
+
+ private class Binding(old: Option[T]) extends super.Binding
+ { def restore() { bound.set(old); } }
+ protected def capture: super.Binding = new Binding(bound.get);
+}
+