Skip to content

Commit

Permalink
Added performance management utils.
Browse files Browse the repository at this point in the history
* Px
* Reusable
* ReusableFn
* ReusableVar

Pertains to japgolly#9, japgolly#85, japgolly#88
  • Loading branch information
japgolly committed Apr 24, 2015
1 parent fb441d3 commit f701bfd
Show file tree
Hide file tree
Showing 14 changed files with 775 additions and 7 deletions.
30 changes: 30 additions & 0 deletions bin/gen-px
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/bin/env scala
// vim: set ft=scala :

val comma = ", "
def T(i: Int) = (64+i).toChar.toString

var ra = Seq.empty[String]

for (i <- (2 to 22)) {
// def m (f: Int => String): List[String] = (1 to a).toList.map(f)
// def mc(f: Int => String): String = m(f).mkString(comma)
// def mt(f: Int => String): String = m(f).mkString("(",comma,")")

def A(j: Int) = ('A'+j).toChar.toString
def a(j: Int) = ('a'+j).toChar.toString
val As = (0 until i) map A
val as = (0 until i) map a
val Ac = As mkString ","
val ac = as mkString ","

def args = (0 until i).map(j => s"p${('a'+j).toChar}:Px[${('A'+j).toChar}]") mkString ", "
def exts = as.map(a => s"$a←p$a") mkString ";"

ra :+= s"""
| @inline def apply$i[$Ac,Z]($args)(z:($Ac)⇒Z): Px[Z] =
| for {$exts} yield z($ac)
""".stripMargin
}
print(ra mkString "")
println()
40 changes: 40 additions & 0 deletions bin/gen-reusable
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/bin/env scala
// vim: set ft=scala :

val comma = ", "
def T(i: Int) = (64+i).toChar.toString

var rt = Seq.empty[String]
var ra = Seq.empty[String]

for (i <- (2 to 22)) {
// def m (f: Int => String): List[String] = (1 to a).toList.map(f)
// def mc(f: Int => String): String = m(f).mkString(comma)
// def mt(f: Int => String): String = m(f).mkString("(",comma,")")

def A(j: Int) = ('A'+j).toChar.toString
def a(j: Int) = ('a'+j).toChar.toString
val As = (0 until i) map A
val as = (0 until i) map a
val Ac = As mkString ","

def AR = As.map(X => s"$X:Reusable") mkString ", "
// def fs = As.map(X => s"${X.toLowerCase}←$X") mkString ";"

def tupleImpl = (1 to i).map(j => s"(x._$j ~=~ y._$j)") mkString " && "

rt :+= s"""
| implicit def tuple$i[$AR]: Reusable[($Ac)] =
| fn((x,y) ⇒ $tupleImpl)
""".stripMargin

ra :+= s"""
| def caseclass$i[$AR, Z](z: Z ⇒ Option[($Ac)]): Reusable[Z] =
| Reusable[($Ac)].contramap(z(_).get)
""".stripMargin


}
print(rt mkString "")
print(ra mkString "")
println()
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ abstract class Implicits extends LowPri {
@inline implicit final def _react_nodeSeq [A <% TagMod](xs: Seq[A]) : TagMod = new SeqNode(xs)
@inline implicit final def _react_nodeArray[A <% TagMod](xs: Array[A]) : TagMod = new SeqNode[A](xs.toSeq)
@inline implicit final def _react_nodeOptional[T[_], A](t: T[A])(implicit o: Optional[T], f: A => TagMod): TagMod =
o.fold(t, f, EmptyTag)
o.fold(t, EmptyTag)(f)

// Scalatags misc
@inline implicit final def _react_styleOrdering : Ordering[Style] = Scalatags.styleOrdering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import scala.scalajs.js.UndefOr

trait Optional[T[_]] {
def foreach[A](t: T[A])(f: A => Unit): Unit
def fold[A, B](t: T[A], f: A => B, b: => B): B
def fold[A, B](t: T[A], b: => B)(f: A => B): B
def isEmpty[A](t: T[A]): Boolean
}

object Optional {
implicit val optionInstance: Optional[Option] = new Optional[Option] {
@inline final override def foreach[A](t: Option[A])(f: (A) => Unit): Unit = t foreach f
@inline final override def fold[A, B](t: Option[A], f: A => B, b: => B): B = t.fold(b)(f)
@inline final override def fold[A, B](t: Option[A], b: => B)(f: A => B): B = t.fold(b)(f)
@inline final override def isEmpty[A](t: Option[A]): Boolean = t.isEmpty
}

implicit val jsUndefOrInstance: Optional[UndefOr] = new Optional[UndefOr] {
@inline final override def foreach[A](t: UndefOr[A])(f: (A) => Unit): Unit = t foreach f
@inline final override def fold[A, B](t: UndefOr[A], f: A => B, b: => B): B = t.fold(b)(f)
@inline final override def fold[A, B](t: UndefOr[A], b: => B)(f: A => B): B = t.fold(b)(f)
@inline final override def isEmpty[A](t: UndefOr[A]): Boolean = t.isEmpty
}
}
10 changes: 10 additions & 0 deletions extra/PERF.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Performance Management
======================

PX

Reusable

ReusableFn

ReusableVar
5 changes: 4 additions & 1 deletion extra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ scalajs-react Extras
A collection of functionality that provides benefit when using scalajs-react.

- [Router](https://github.com/japgolly/scalajs-react/blob/master/extra/ROUTER.md)
- [Performance Management](https://github.com/japgolly/scalajs-react/blob/master/extra/PERF.md)

On this page:
- [ExternalVar](#externalvar)
- Component Mixins:
- [Broadcaster and Listenable](#broadcaster-and-listenable)
- [ExternalVar](#externalvar)
- [LogLifecycle](#loglifecycle)
- [OnUnmount](#onunmount)
- [SetInterval](#setinterval)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package japgolly.scalajs.react.extra

import japgolly.scalajs.react._, ScalazReact._
import monocle.Lens
import scalaz.effect.IO
import japgolly.scalajs.react._, ScalazReact._

/**
* Provides a component with safe R/W access to an external variable.
*
* Use [[ReusableVar]] for a [[Reusable]] version of this.
*/
final class ExternalVar[A](val value: A, val set: A => IO[Unit]) {

Expand Down
239 changes: 239 additions & 0 deletions extra/src/main/scala/japgolly/scalajs/react/extra/Px.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
package japgolly.scalajs.react.extra

/**
* A mechanism for caching data with dependencies.
*
* This is basically a performance-focused, lightweight implementation of pull-based
* <a href="http://en.wikipedia.org/wiki/Functional_reactive_programming">FRP</a>,
* pull-based meaning that in the chain A→B→C, an update to A doesn't affect C until the value of C is requested.
*
* What does Px mean? I don't know, I just needed a name and I liked the way @lihaoyi's Rx type name looked in code.
* You can consider this "Performance eXtension". If this were Java it'd be named
* `AutoRefreshOnRequestDependentCachedVariable`.
*/
sealed abstract class Px[A] {

/** Revision of its value. Increments when value changes. */
def rev: Int

/** Get the latest value, updating itself if necessary. */
def value(): A

/** Get the last used value without updating. */
def peek: A

final def valueSince(r: Int): Option[A] = {
val v = value() // Ensure value and rev are up-to-date
if (rev != r)
Some(v)
else
None
}

def map[B](f: A => B): Px[B] =
new Px.Map(this, f)

def flatMap[B](f: A => Px[B]): Px[B] =
new Px.FlatMap(this, f)

// override def toString = value().toString
}

object Px {
sealed abstract class Root[A] extends Px[A] {
protected val ignoreChange: (A, A) => Boolean

protected var _rev = 0
protected var _value: A

override final def rev = _rev

override final def peek = _value

protected def setMaybe(a: A): Unit =
if (!ignoreChange(_value, a)) {
_rev += 1
_value = a
}
}

/**
* A variable in the traditional sense.
*
* Doesn't change until you explicitly call `set()`.
*/
final class Var[A](initialValue: A, protected val ignoreChange: (A, A) => Boolean) extends Root[A] {
override protected var _value = initialValue

override def value() = _value

def set(a: A): Unit =
setMaybe(a)
}

/**
* The value of a zero-param function.
*
* The `M` in `ThunkM` denotes "Manual refresh", meaning that the value will not update until you explicitly call
* `refresh()`.
*/
final class ThunkM[A](next: () => A, protected val ignoreChange: (A, A) => Boolean) extends Root[A] {
override protected var _value = next()

override def value() = _value

def refresh(): Unit =
setMaybe(next())
}

/**
* The value of a zero-param function.
*
* The `A` in `ThunkA` denotes "Auto refresh", meaning that the function will be called every time the value is
* requested, and the value updated if necessary.
*/
final class ThunkA[A](next: () => A, protected val ignoreChange: (A, A) => Boolean) extends Root[A] {
override protected var _value = next()

override def value() = {
setMaybe(next())
_value
}
}

/**
* A value `B` dependent on the value of some `Px[A]`.
*/
final class Map[A, B](xa: Px[A], f: A => B) extends Px[B] {
private var _value = f(xa.value())
private var _revA = xa.rev

override def rev = _revA
override def peek = _value

override def map[C](g: B => C): Px[C] =
new Map(xa, g compose f)

override def flatMap[C](g: B => Px[C]): Px[C] =
new Px.FlatMap(xa, g compose f)

override def value(): B = {
xa.valueSince(_revA).foreach { a =>
_value = f(a)
_revA = xa.rev
}
_value
}
}

/**
* A `Px[B]` dependent on the value of some `Px[A]`.
*/
final class FlatMap[A, B](xa: Px[A], f: A => Px[B]) extends Px[B] {
private var _value = f(xa.value())
private var _revA = xa.rev

override def rev = _revA + _value.rev
override def peek = _value.peek

override def value(): B = {
xa.valueSince(_revA).foreach { a =>
_value = f(a)
_revA = xa.rev
}
_value.value()
}
}

// ===================================================================================================================

implicit def reusability[A]: Reusable[Px[A]] =
Reusable.fn((x, y) => (x eq y) && (x.rev ~=~ y.rev))

/** Import this to avoid the need to call `.value()` on your `Px`s. */
object AutoValue {
@inline implicit def autoPxValue[A](x: Px[A]): A = x.value()
}

/** Refresh multiple [[ThunkM]]s at once. */
@inline def refresh(xs: ThunkM[_]*): Unit =
xs.foreach(_.refresh())

// ===================================================================================================================

private val noReuse: (Any, Any) => Boolean =
(_, _) => false

def apply [A](a: A) = new Var(a, noReuse)
def thunkM[A](f: => A) = new ThunkM(() => f, noReuse)
def thunkA[A](f: => A) = new ThunkA(() => f, noReuse)

def reusableVar [A](a: A) (implicit r: Reusable[A]) = new Var(a, r.test)
def reusableThunkM[A](f: => A)(implicit r: Reusable[A]) = new ThunkM(() => f, r.test)
def reusableThunkA[A](f: => A)(implicit r: Reusable[A]) = new ThunkA(() => f, r.test)

// Generated by bin/gen-px

@inline def apply2[A,B,Z](pa:Px[A], pb:Px[B])(z:(A,B)Z): Px[Z] =
for {apa;bpb} yield z(a,b)

@inline def apply3[A,B,C,Z](pa:Px[A], pb:Px[B], pc:Px[C])(z:(A,B,C)Z): Px[Z] =
for {apa;bpb;cpc} yield z(a,b,c)

@inline def apply4[A,B,C,D,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D])(z:(A,B,C,D)Z): Px[Z] =
for {apa;bpb;cpc;dpd} yield z(a,b,c,d)

@inline def apply5[A,B,C,D,E,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E])(z:(A,B,C,D,E)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe} yield z(a,b,c,d,e)

@inline def apply6[A,B,C,D,E,F,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F])(z:(A,B,C,D,E,F)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf} yield z(a,b,c,d,e,f)

@inline def apply7[A,B,C,D,E,F,G,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G])(z:(A,B,C,D,E,F,G)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg} yield z(a,b,c,d,e,f,g)

@inline def apply8[A,B,C,D,E,F,G,H,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H])(z:(A,B,C,D,E,F,G,H)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph} yield z(a,b,c,d,e,f,g,h)

@inline def apply9[A,B,C,D,E,F,G,H,I,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I])(z:(A,B,C,D,E,F,G,H,I)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi} yield z(a,b,c,d,e,f,g,h,i)

@inline def apply10[A,B,C,D,E,F,G,H,I,J,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J])(z:(A,B,C,D,E,F,G,H,I,J)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj} yield z(a,b,c,d,e,f,g,h,i,j)

@inline def apply11[A,B,C,D,E,F,G,H,I,J,K,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K])(z:(A,B,C,D,E,F,G,H,I,J,K)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk} yield z(a,b,c,d,e,f,g,h,i,j,k)

@inline def apply12[A,B,C,D,E,F,G,H,I,J,K,L,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L])(z:(A,B,C,D,E,F,G,H,I,J,K,L)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl} yield z(a,b,c,d,e,f,g,h,i,j,k,l)

@inline def apply13[A,B,C,D,E,F,G,H,I,J,K,L,M,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m)

@inline def apply14[A,B,C,D,E,F,G,H,I,J,K,L,M,N,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n)

@inline def apply15[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o)

@inline def apply16[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p)

@inline def apply17[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P], pq:Px[Q])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp;qpq} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q)

@inline def apply18[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P], pq:Px[Q], pr:Px[R])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp;qpq;rpr} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r)

@inline def apply19[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P], pq:Px[Q], pr:Px[R], ps:Px[S])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp;qpq;rpr;sps} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s)

@inline def apply20[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P], pq:Px[Q], pr:Px[R], ps:Px[S], pt:Px[T])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp;qpq;rpr;sps;tpt} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t)

@inline def apply21[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P], pq:Px[Q], pr:Px[R], ps:Px[S], pt:Px[T], pu:Px[U])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp;qpq;rpr;sps;tpt;upu} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)

@inline def apply22[A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,Z](pa:Px[A], pb:Px[B], pc:Px[C], pd:Px[D], pe:Px[E], pf:Px[F], pg:Px[G], ph:Px[H], pi:Px[I], pj:Px[J], pk:Px[K], pl:Px[L], pm:Px[M], pn:Px[N], po:Px[O], pp:Px[P], pq:Px[Q], pr:Px[R], ps:Px[S], pt:Px[T], pu:Px[U], pv:Px[V])(z:(A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V)Z): Px[Z] =
for {apa;bpb;cpc;dpd;epe;fpf;gpg;hph;ipi;jpj;kpk;lpl;mpm;npn;opo;ppp;qpq;rpr;sps;tpt;upu;vpv} yield z(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v)
}
Loading

0 comments on commit f701bfd

Please sign in to comment.