de.sciss.lucre.data.Ancestor.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lucre-data_2.11 Show documentation
Show all versions of lucre-data_2.11 Show documentation
Extension of Scala-STM, adding optional durability layer, and providing API for confluent and reactive event layers
The newest version!
/*
* Ancestor.scala
* (Lucre)
*
* Copyright (c) 2009-2019 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU Lesser General Public License v2.1+
*
*
* For further information, please contact Hanns Holger Rutz at
* [email protected]
*/
package de.sciss.lucre.data
import de.sciss.lucre.data.TotalOrder.Set
import de.sciss.lucre.geom.IntSpace.ThreeDim
import de.sciss.lucre.geom.{DistanceMeasure, IntCube, IntDistanceMeasure3D, IntPoint3D, IntSpace}
import de.sciss.lucre.stm.{Disposable, Base}
import de.sciss.serial.{DataInput, DataOutput, Serializer, Writable}
object Ancestor {
private final val SER_VERSION = 65
private[Ancestor] val cube = IntCube(0x40000000, 0x40000000, 0x40000000, 0x40000000)
private type TreeOrder[S <: Base[S]] = TotalOrder.Set.Entry[S]
object Vertex {
private[Ancestor] implicit def toPoint[S <: Base[S], Version](v: Vertex[S, Version], tx: S#Tx): IntPoint3D =
IntPoint3D(v.pre.tag(tx), v.post.tag(tx), v.versionInt)
}
sealed trait Vertex[S <: Base[S], Version] extends Writable with Disposable[S#Tx] {
// ---- abstract ----
def version: Version
private[Ancestor] def pre: TreeOrder[S]
private[Ancestor] def post: TreeOrder[S]
private[Ancestor] def tree: Tree[S, Version]
// ---- implementation ----
final def isAncestorOf(that: Vertex[S, Version])(implicit tx: S#Tx): Boolean =
versionInt <= that.versionInt &&
pre .compare(that.pre ) <= 0 &&
post.compare(that.post) >= 0
final def versionInt: Int = tree.intView(version)
final def write(out: DataOutput): Unit = {
tree.versionSerializer.write(version, out)
pre .write(out)
post.write(out)
}
final def dispose()(implicit tx: S#Tx): Unit = {
pre .dispose()
post.dispose()
}
override def toString = s"Vertex($version)"
}
implicit def treeSerializer[S <: Base[S], Version](
implicit versionSerializer: Serializer[S#Tx, S#Acc, Version],
intView: Version => Int): Serializer[S#Tx, S#Acc, Tree[S, Version]] = new TreeSer[S, Version]
def newTree[S <: Base[S], Version](rootVersion: Version)(
implicit tx: S#Tx, versionSerializer: Serializer[S#Tx, S#Acc, Version],
intView: Version => Int): Tree[S, Version] = {
new TreeNew[S, Version](rootVersion, tx)
}
def readTree[S <: Base[S], Version](in: DataInput, access: S#Acc)(
implicit tx: S#Tx, versionSerializer: Serializer[S#Tx, S#Acc, Version],
intView: Version => Int): Tree[S, Version] = {
new TreeRead[S, Version](in, access, tx)
}
private final class TreeSer[S <: Base[S], Version](implicit versionSerializer: Serializer[S#Tx, S#Acc, Version],
versionView: Version => Int)
extends Serializer[S#Tx, S#Acc, Tree[S, Version]] {
def write(t: Tree[S, Version], out: DataOutput): Unit = t.write(out)
def read(in: DataInput, access: S#Acc)(implicit tx: S#Tx): Tree[S, Version] =
new TreeRead[S, Version](in, access, tx)
override def toString = "Ancestor.treeSerializer"
}
private sealed trait TreeImpl[S <: Base[S], Version] extends Tree[S, Version] {
me =>
// ---- abstract ----
protected def order: TotalOrder.Set[S]
// ---- implementation ----
override def toString = s"Ancestor.Tree(root=$root)"
implicit protected object VertexSerializer extends Serializer[S#Tx, S#Acc, K] {
def write(v: K, out: DataOutput): Unit = v.write(out)
def read(in: DataInput, access: S#Acc)(implicit tx: S#Tx): K = new K {
def tree: Tree[S, Version] = me
val version : Version = versionSerializer.read(in, access)
val pre : Set.Entry[S] = order.readEntry(in, access)
val post : Set.Entry[S] = order.readEntry(in, access)
}
}
final def write(out: DataOutput): Unit = {
out.writeByte(SER_VERSION)
order.write(out)
root.write(out)
}
final def dispose()(implicit tx: S#Tx): Unit = {
order.dispose()
root.dispose()
}
final def vertexSerializer: Serializer[S#Tx, S#Acc, K] = VertexSerializer
final def insertChild(parent: K, newChild: Version)(implicit tx: S#Tx): K = new K {
def tree: Tree[S, Version] = me
val version : Version = newChild
val pre : Set.Entry[S] = parent.pre.append()
val post : Set.Entry[S] = pre.append()
}
final def insertRetroChild(parent: K, newChild: Version)(implicit tx: S#Tx): K = new K {
def tree: Tree[S, Version] = me
val version : Version = newChild
val pre : Set.Entry[S] = parent.pre .append ()
val post : Set.Entry[S] = parent.post.prepend()
override def toString = s"${super.toString}@r-ch"
}
final def insertRetroParent(child: K, newParent: Version)(implicit tx: S#Tx): K = {
require(child != root)
new K {
def tree: Tree[S, Version] = me
val version : Version = newParent
val pre : Set.Entry[S] = child.pre .prepend()
val post : Set.Entry[S] = child.post.append ()
override def toString = s"${super.toString}@r-par"
}
}
}
private final class TreeNew[S <: Base[S], Version](rootVersion: Version, tx0: S#Tx)(
implicit val versionSerializer: Serializer[S#Tx, S#Acc, Version], val intView: Version => Int)
extends TreeImpl[S, Version] {
me =>
protected val order: TotalOrder.Set[S] = TotalOrder.Set.empty(0)(tx0)
val root: K = new K {
def tree: Tree[S, Version] = me
def version : Version = rootVersion
val pre : Set.Entry[S] = order.root
val post : Set.Entry[S] = pre.appendMax()(tx0)
}
}
private final class TreeRead[S <: Base[S], Version](in: DataInput, access: S#Acc, tx0: S#Tx)(
implicit val versionSerializer: Serializer[S#Tx, S#Acc, Version], val intView: Version => Int)
extends TreeImpl[S, Version] {
{
val serVer = in.readByte()
if (serVer != SER_VERSION)
sys.error(s"Incompatible serialized version (found $serVer, required $SER_VERSION).")
}
protected val order: TotalOrder.Set[S] = TotalOrder.Set.read(in, access)(tx0)
val root: K = VertexSerializer.read(in, access)(tx0)
}
sealed trait Tree[S <: Base[S], Version] extends Writable with Disposable[S#Tx] {
protected type K = Vertex[S, Version]
private[Ancestor] def versionSerializer: Serializer[S#Tx, S#Acc, Version]
private[Ancestor] def intView: Version => Int
def vertexSerializer: Serializer[S#Tx, S#Acc, K]
def root: K
def insertChild (parent: K, newChild : Version)(implicit tx: S#Tx): K
def insertRetroChild (parent: K, newChild : Version)(implicit tx: S#Tx): K
def insertRetroParent(child : K, newParent: Version)(implicit tx: S#Tx): K
}
private type MarkOrder[S <: Base[S], Version, A] = TotalOrder.Map.Entry[S, Mark[S, Version, A]]
private final val chebyMetric = IntDistanceMeasure3D.chebyshevXY
// left-bottom-front
// = left in pre-order list, right in post-order list, smaller in version
private final val metric = chebyMetric.orthant(2)
private final class FilterMetric(pred: Int => Boolean) extends IntDistanceMeasure3D.LongImpl {
import IntSpace.ThreeDim.{HyperCube, PointLike}
override def toString = s"Ancestor.FilterMetric@${pred.hashCode.toHexString}"
def distance(a: PointLike, b: PointLike): Long = {
if (b.x <= a.x && b.y >= a.y && pred(b.z)) {
chebyMetric.distance(a, b)
} else maxValue
}
def minDistance(p: PointLike, q: HyperCube): Long = {
val qe = q.extent
val qem1 = qe - 1
if (q.cx - qe <= p.x && q.cy + qem1 >= p.y && pred(q.cz - qe)) {
chebyMetric.minDistance(p, q)
} else maxValue
}
def maxDistance(p: PointLike, q: HyperCube): Long = {
val qe = q.extent
val qem1 = qe - 1
if (q.cx + qem1 <= p.x && q.cy - qe >= p.y && pred(q.cz + qem1)) {
chebyMetric.maxDistance(p, q)
} else maxValue
}
}
private sealed trait Mark[S <: Base[S], Version, /* @spec(ValueSpec) */ A] extends Writable {
// ---- abstract ----
def fullVertex: Vertex[S, Version]
def pre: MarkOrder[S, Version, A]
def post: MarkOrder[S, Version, A]
def value: A
def map: MapImpl[S, Version, A]
// ---- implementation ----
final def toPoint(implicit tx: S#Tx): IntPoint3D = IntPoint3D(pre.tag, post.tag, fullVertex.versionInt)
final def write(out: DataOutput): Unit = {
fullVertex.write(out)
pre .write(out)
post .write(out)
map.valueSerializer.write(value, out)
}
final def removeAndDispose()(implicit tx: S#Tx): Unit = {
map.skip.remove(this)
pre .removeAndDispose()
post.removeAndDispose()
}
override def toString = s"Mark(${fullVertex.version} -> $value)"
}
def newMap[S <: Base[S], Version, /* @spec(ValueSpec) */ A](full: Tree[S, Version], rootVertex: Vertex[S, Version],
rootValue: A)(implicit tx: S#Tx, valueSerializer: Serializer[S#Tx, S#Acc, A]): Map[S, Version, A] = {
new MapNew[S, Version, A](full, rootVertex, rootValue, tx, valueSerializer)
}
def readMap[S <: Base[S], Version, /* @spec(ValueSpec) */ A](in: DataInput, access: S#Acc, full: Tree[S, Version])(
implicit tx: S#Tx, valueSerializer: Serializer[S#Tx, S#Acc, A]): Map[S, Version, A] = {
new MapRead[S, Version, A](full, in, access, tx, valueSerializer)
}
/*
* The result of isomorphic search (mapping full tree vertex coordinates to marked tree coordinates).
*
* @param pre the nearest mark in the pre-order traversal
* @param preCmp the relation between the query (full) vertex and the found mark vertex.
* `-1` indicates that the full vertex lies left of the found mark vertex in the pre-order list,
* `0` indicates that both refer to the same version, and `1` indicates that the full vertex lies
* right to the mark vertex in the pre-order list
* @param post the nearest mark in the post-order traversal
* @param postCmp the relation between the query (full) vertex and the found mark vertex.
* `-1` indicates that the full vertex lies left of the found mark vertex in the post-order list,
* `0` indicates that both refer to the same version, and `1` indicates that the full vertex lies
* right to the mark vertex in the post-order list
*/
private final class IsoResult[S <: Base[S], Version, /* @spec(ValueSpec) */ A](val pre: Mark[S, Version, A],
val preCmp: Int,
val post: Mark[S, Version, A],
val postCmp: Int) {
override def toString: String = {
val preS = if (preCmp < 0) "< " else if (preCmp > 0) "> " else "== "
val postS = if (postCmp < 0) "< " else if (postCmp > 0) "> " else "== "
s"Iso(pre $preS$pre,post $postS$post)"
}
}
private sealed trait MapImpl[S <: Base[S], Version, /* @spec(ValueSpec) */ A]
extends Map[S, Version, A] with TotalOrder.Map.RelabelObserver[S#Tx, Mark[S, Version, A]] {
me =>
final type M = Mark[S, Version, A]
// ---- abstract ----
protected def preOrder: TotalOrder.Map[S, M]
protected def postOrder: TotalOrder.Map[S, M]
protected def preList: SkipList .Set[S, M]
protected def postList: SkipList .Set[S, M]
private[Ancestor] def skip: SkipOctree[S, IntSpace.ThreeDim, M]
// ---- implementation ----
override def toString = s"Ancestor.Map(tree=$full)"
protected final def preOrdering: Ordering[S#Tx, M] = new Ordering[S#Tx, M] {
def compare(a: M, b: M)(implicit tx: S#Tx): Int = a.pre compare b.pre
}
protected final def postOrdering: Ordering[S#Tx, M] = new Ordering[S#Tx, M] {
def compare(a: M, b: M)(implicit tx: S#Tx): Int = a.post compare b.post
}
protected implicit object markSerializer extends Serializer[S#Tx, S#Acc, M] {
def write(v: M, out: DataOutput): Unit = v.write(out)
def read(in: DataInput, access: S#Acc)(implicit tx: S#Tx): M = new M {
def map: MapImpl[S, Version, A] = me
val fullVertex: Vertex[S, Version] = full.vertexSerializer.read(in, access)
val pre : MarkOrder[S, Version, A] = preOrder .readEntry(in, access)
val post : MarkOrder[S, Version, A] = postOrder .readEntry(in, access)
val value : A = valueSerializer .read (in, access)
}
}
final def write(out: DataOutput): Unit = {
out.writeByte(SER_VERSION)
// note: we ask for the full tree through the serializer constructor,
// thus we omit writing it out ourselves
preOrder .write(out)
postOrder.write(out)
preList .write(out)
postList .write(out)
skip .write(out)
// root.write( out )
}
final def dispose()(implicit tx: S#Tx): Unit = {
preOrder.dispose()
postOrder.dispose()
preList.dispose()
postList.dispose()
skip.dispose()
}
final def add(entry: (K, A))(implicit tx: S#Tx): Boolean = {
val vertex = entry._1
val iso0 = query(vertex)
val iso = if (iso0.preCmp != 0) iso0 else {
// replace existing entry
// XXX TODO -- we could use
// the iso's pre and succ pointers,
// but it's getting nasty, so we'll...
val old: M = new M {
def map: MapImpl[S, Version, A] = me
val fullVertex: K = vertex
val value : A = entry._2
val pre : MarkOrder[S, Version, A] = iso0.pre.pre
val post : MarkOrder[S, Version, A] = iso0.pre.post
}
assert(preList .remove(old))
assert(postList.remove(old))
assert(skip .remove(old))
iso0.pre.removeAndDispose() // iso.pre is a VM!
// ...so we'll just repeat the search for the sake of simplicity.
query(vertex)
}
val mv: M = new M {
def map : MapImpl[S, Version, A] = me
val fullVertex : K = vertex
val value : A = entry._2
val pre : MarkOrder[S, Version, A] = preOrder .insert()
val post : MarkOrder[S, Version, A] = postOrder.insert()
if (iso.preCmp <= 0) {
preOrder .placeBefore(iso.pre , this)
} else {
preOrder .placeAfter (iso.pre , this)
}
if (iso.postCmp <= 0) {
postOrder.placeBefore(iso.post, this)
} else {
postOrder.placeAfter (iso.post, this)
}
}
preList += mv
postList += mv
skip.add(mv)
// val errors = skip.asInstanceOf[DeterministicSkipOctree[S, _, _]].verifyConsistency(reportOnly = true)
// require(errors.isEmpty, errors.mkString("==== ERRORS FOUND ====" ,"\n", ""))
// res
}
final def +=(entry: (K, A))(implicit tx: S#Tx): this.type = {
add(entry)
this
}
private[this] def query(vertex: K)(implicit tx: S#Tx): IsoResult[S, Version, A] = {
val cfPre = vertex.pre
val (cmPreN, cmPreCmp) = preList.isomorphicQuery(new Ordered[S#Tx, M] {
def compare(that: M)(implicit tx: S#Tx): Int = {
cfPre.compare(that.fullVertex.pre)
}
})
if (cmPreCmp == 0) return new IsoResult(cmPreN, 0, cmPreN, 0)
val cfPost = vertex.post
val (cmPostN, cmPostCmp) = postList.isomorphicQuery(new Ordered[S#Tx, M] {
def compare(that: M)(implicit tx: S#Tx): Int = {
cfPost.compare(that.fullVertex.post)
}
})
new IsoResult(cmPreN, cmPreCmp, cmPostN, cmPostCmp)
}
final def remove(vertex: K)(implicit tx: S#Tx): Boolean = {
val iso = query(vertex)
iso.preCmp == 0 /* && (iso.postCmp == 0) */ && {
// assert(iso.postCmp == 0)
iso.pre.removeAndDispose() // iso.pre is a VM!
true
}
}
final def -=(vertex: K)(implicit tx: S#Tx): this.type = {
remove(vertex)
this
}
final def get(vertex: K)(implicit tx: S#Tx): Option[A] = {
val iso = query(vertex)
if (iso.preCmp == 0) {
// assert(iso.postCmp == 0)
Some(iso.pre.value)
} else None
}
// XXX TODO: DRY
final def nearest(vertex: K)(implicit tx: S#Tx): (K, A) = {
val iso = query(vertex)
if (iso.preCmp == 0) {
// assert(iso.postCmp == 0)
(vertex, iso.pre.value)
} else {
val preTag = iso.pre .pre .tag
val postTag = iso.post.post.tag
val x = if (iso.preCmp < 0) preTag - 1 else preTag
val y = if (iso.postCmp > 0) postTag + 1 else postTag
val nn = skip.nearestNeighbor(IntPoint3D(x, y, vertex.versionInt), metric)
(nn.fullVertex, nn.value)
}
}
final def nearestOption(vertex: K)(implicit tx: S#Tx): Option[(K, A)] = {
val iso = query(vertex)
if (iso.preCmp == 0) {
// assert(iso.postCmp == 0)
Some((vertex, iso.pre.value))
} else {
nearestWithMetric(vertex, iso, metric)
}
}
final def nearestWithFilter(vertex: K)(p: Int => Boolean)(implicit tx: S#Tx): Option[(K, A)] = {
val iso = query(vertex)
nearestWithMetric(vertex, iso, new FilterMetric(p))
}
private[this] def nearestWithMetric(vertex: K, iso: IsoResult[S, Version, A],
metric: DistanceMeasure[Long, ThreeDim])
(implicit tx: S#Tx): Option[(K, A)] = {
val preTag = iso.pre .pre .tag
val postTag = iso.post.post.tag
val x = if (iso.preCmp < 0) preTag - 1 else preTag
val y = if (iso.postCmp > 0) postTag + 1 else postTag
val nnOpt: Option[M] = skip.nearestNeighborOption[Long](IntPoint3D(x, y, vertex.versionInt), metric)
nnOpt.map { nn => (nn.fullVertex, nn.value) }
}
// ---- RelabelObserver ----
final def beforeRelabeling(iter: Iterator[M])(implicit tx: S#Tx): Unit =
iter.foreach(skip -= _)
final def afterRelabeling(iter: Iterator[M])(implicit tx: S#Tx): Unit =
iter.foreach(skip += _)
final def debugPrint(implicit tx: S#Tx): String = {
val s = skip.toList.map { m =>
val v = m.fullVertex
s"{version = ${v.versionInt}, value = ${m.value}, pre = ${v.pre.tag}, post = ${v.post.tag}}"
}
s.mkString("[", ", ", "]")
}
}
private final class MapNew[S <: Base[S], Version, A](val full: Tree[S, Version],
rootVertex: Vertex[S, Version],
rootValue: A, tx0: S#Tx,
val valueSerializer: Serializer[S#Tx, S#Acc, A])
extends MapImpl[S, Version, A] {
me =>
protected val preOrder: TotalOrder.Map[S, M] =
TotalOrder.Map.empty[S, M](me, _.pre)(tx0, markSerializer)
protected val postOrder: TotalOrder.Map[S, M] =
TotalOrder.Map.empty[S, M](me, _.post, rootTag = Int.MaxValue)(tx0, markSerializer)
private[Ancestor] val skip: SkipOctree[S, IntSpace.ThreeDim, M] = {
val pointView = (p: M, tx: S#Tx) => p.toPoint(tx)
SkipOctree.empty[S, IntSpace.ThreeDim, M](cube)(tx0, pointView, IntSpace.ThreeDim, markSerializer)
}
protected val root: M = {
val res: M = new M {
def map : MapImpl[S, Version, A] = me
val fullVertex : K = rootVertex
def pre : MarkOrder[S, Version, A] = preOrder .root
def post : MarkOrder[S, Version, A] = postOrder.root
val value : A = rootValue
override def toString = s"Root($value)"
}
(skip += res)(tx0)
res
}
protected val preList: SkipList.Set[S, M] = {
implicit val ord: Ordering[S#Tx, M] = preOrdering
implicit val tx: S#Tx = tx0
val res = SkipList.Set.empty[S, M]
res.add(root)
res
}
protected val postList: SkipList.Set[S, M] = {
implicit val ord: Ordering[S#Tx, M] = postOrdering
implicit val tx: S#Tx = tx0
val res = SkipList.Set.empty[S, M]
res.add(root)
res
}
}
private final class MapRead[S <: Base[S], Version, A](val full: Tree[S, Version], in: DataInput,
access: S#Acc, tx0: S#Tx,
val valueSerializer: Serializer[S#Tx, S#Acc, A])
extends MapImpl[S, Version, A] {
me =>
{
val serVer = in.readByte()
if (serVer != SER_VERSION)
sys.error(s"Incompatible serialized version (found $serVer, required $SER_VERSION).")
}
protected val preOrder: TotalOrder.Map[S, M] =
TotalOrder.Map.read[S, M](in, access, me, _.pre)(tx0, markSerializer)
protected val postOrder: TotalOrder.Map[S, M] =
TotalOrder.Map.read[S, M](in, access, me, _.post)(tx0, markSerializer)
protected val preList: SkipList.Set[S, M] = {
implicit val ord: Ordering[S#Tx, M] = preOrdering
implicit val tx: S#Tx = tx0
SkipList.Set.read[S, M](in, access)
}
protected val postList: SkipList.Set[S, M] = {
implicit val ord: Ordering[S#Tx, M] = postOrdering
implicit val tx: S#Tx = tx0
SkipList.Set.read[S, M](in, access)
}
private[Ancestor] val skip: SkipOctree[S, IntSpace.ThreeDim, M] = {
val pointView = (p: M, tx: S#Tx) => p.toPoint(tx)
SkipOctree.read[S, IntSpace.ThreeDim, M](in, access)(tx0, pointView, IntSpace.ThreeDim, markSerializer)
}
}
sealed trait Map[S <: Base[S], Version, /* @spec(ValueSpec) */ A] extends Writable with Disposable[S#Tx] {
type K = Vertex[S, Version]
def full: Tree[S, Version]
def debugPrint(implicit tx: S#Tx): String
/**
* Marks a given key with a given value.
*
* @param entry the key-value pair (where the key is a vertex in the full tree)
* @return `true` if the mark is new, `false` if there had been a mark for the given vertex.
*/
def add(entry: (K, A))(implicit tx: S#Tx): Boolean
def +=(entry: (K, A))(implicit tx: S#Tx): this.type
def remove(vertex: K)(implicit tx: S#Tx): Boolean
def -=(vertex: K)(implicit tx: S#Tx): this.type
/**
* Queries for a mark at a given version vertex. Unlike `nearest`, this does
* not search in the map, but merely tests if the given vertex has been
* marked or not.
*
* @param vertex the version vertex to look up
* @return the value associated with that vertex, or `None` if the vertex is unmarked.
*/
def get(vertex: K)(implicit tx: S#Tx): Option[A]
/**
* Finds the nearest marked ancestor of a given version key.
* Since the map is constructed with a defined root value, this method is
* guaranteed to succeed—if there are no other marks in the map,
* it will return the root value (unless the `version` argument is
* illegal, i.e. has a version lower than the root vertex' version).
*
* @param vertex the key to look for. The algorithm searches for
* the nearest ancestor in the marked map with a version less than or
* equal to the given version
* @return a pair consisting of the tree vertex found and the value with which
* it has been marked. If the query `version` vertex was marked, it will be
* that vertex which is returned, and not an ancestor.
*/
def nearest(vertex: K)(implicit tx: S#Tx): (K, A)
def nearestOption(vertex: K)(implicit tx: S#Tx): Option[(K, A)]
/**
* Searches for the nearest marked ancestor, where version control is handed over
* to a custom predicate function. I.e., while `nearestOption` will test for a
* version that is less than or equal to the query version, the behaviour may be
* customised here. The predicate function is called with the `versionInt` field
* of the vertices, e.g. using the tree's `intView`.
*
* Only those vertices are considered for which the predicate is `true`.
*
* '''Note:''' This currently only works correctly if the predicate tests for
* version anteriority!
*
* @param vertex the query vertex
* @param p the predicate function for the integer view of the vertex versions
*/
def nearestWithFilter(vertex: K)(p: Int => Boolean)(implicit tx: S#Tx): Option[(K, A)]
def valueSerializer: Serializer[S#Tx, S#Acc, A]
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy