Removed Observable from observable project and renamed observable project to cell. Cell is now the most basic interface for users.

This commit is contained in:
Daan Vanden Bosch 2022-06-04 21:33:29 +02:00
parent b389cb9521
commit 82524f1f2d
172 changed files with 846 additions and 748 deletions

View File

@ -1,7 +1,7 @@
# Phantasmal World
Phantasmal World is a collection of software for Phantasy Star Online.
The [web aplication](https://www.phantasmal.world/) has a model viewer, quest editor and hunt
The [web application](https://www.phantasmal.world/) has a model viewer, quest editor and hunt
optimizer. There is also a work-in-progress [PSO server](psoserv/README.md).
## Developers
@ -70,7 +70,7 @@ assembler/disassembler and a work-in-progress script engine/VM. It also has a mo
scripting bytecode and data flow analysis for it. This subproject can be used as a library in other
projects.
#### observable
#### cell
A full-fledged multiplatform implementation of the observer pattern.

View File

@ -1,9 +1,6 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.observable.AbstractDependency
import world.phantasmal.observable.CallbackChangeObserver
import world.phantasmal.observable.ChangeObserver
abstract class AbstractCell<T> : AbstractDependency<T>(), Cell<T> {
override fun observeChange(observer: ChangeObserver<T>): Disposable =

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable
package world.phantasmal.cell
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract

View File

@ -1,8 +1,6 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.core.unsafe.unsafeCast
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Dependent
abstract class AbstractDependentCell<T, Event : ChangeEvent<T>> : AbstractCell<T>(), Dependent {

View File

@ -1,13 +1,9 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.core.unsafe.unsafeAssertNotNull
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.observable.Observable
abstract class AbstractFlatteningDependentCell<T, ComputedCell : Cell<T>, Event : ChangeEvent<T>>(
private val dependencies: Array<out Observable<*>>,
private val dependencies: Array<out Cell<*>>,
private val compute: () -> ComputedCell,
) : AbstractDependentCell<T, Event>() {

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable
package world.phantasmal.cell
import world.phantasmal.core.disposable.TrackedDisposable
import world.phantasmal.core.unsafe.unsafeCast
@ -7,7 +7,7 @@ import world.phantasmal.core.unsafe.unsafeCast
* Calls [callback] when [dependency] changes.
*/
class CallbackChangeObserver<T, E : ChangeEvent<T>>(
private val dependency: Observable<T>,
private val dependency: Cell<T>,
// We don't use ChangeObserver<T> because of type system limitations. It would break e.g.
// AbstractListCell.observeListChange.
private val callback: (E) -> Unit,

View File

@ -1,12 +1,12 @@
package world.phantasmal.observable
package world.phantasmal.cell
import world.phantasmal.core.disposable.TrackedDisposable
/**
* Calls [callback] when one or more observable in [dependencies] changes.
* Calls [callback] when one or more cells in [dependencies] change.
*/
class CallbackObserver(
private vararg val dependencies: Observable<*>,
private vararg val dependencies: Cell<*>,
private val callback: () -> Unit,
) : TrackedDisposable(), Dependent, LeafDependent {

View File

@ -0,0 +1,18 @@
package world.phantasmal.cell
import world.phantasmal.core.disposable.Disposable
import kotlin.reflect.KProperty
/**
* A [value] that can change over time.
*/
interface Cell<out T> : Dependency<T> {
val value: T
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
/**
* [observer] will be called whenever this cell changes.
*/
fun observeChange(observer: ChangeObserver<T>): Disposable
}

View File

@ -1,7 +1,6 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.observable.CallbackObserver
private val TRUE_CELL: Cell<Boolean> = ImmutableCell(true)
private val FALSE_CELL: Cell<Boolean> = ImmutableCell(false)
@ -39,11 +38,14 @@ fun <T> mutableCell(value: T): MutableCell<T> = SimpleCell(value)
fun <T> mutableCell(getter: () -> T, setter: (T) -> Unit): MutableCell<T> =
DelegatingCell(getter, setter)
fun <T> Cell<T>.observe(observer: (T) -> Unit): Disposable =
observeChange { observer(it.value) }
fun <T> Cell<T>.observeNow(
observer: (T) -> Unit,
): Disposable {
val disposable = observeChange { observer(it.value) }
// Call observer after observeChange to avoid double recomputation in most observables.
// Call observer after observeChange to avoid double recomputation in most cells.
observer(value)
return disposable
}
@ -54,7 +56,7 @@ fun <T1, T2> observeNow(
observer: (T1, T2) -> Unit,
): Disposable {
val disposable = CallbackObserver(c1, c2) { observer(c1.value, c2.value) }
// Call observer after observeChange to avoid double recomputation in most observables.
// Call observer after observeChange to avoid double recomputation in most cells.
observer(c1.value, c2.value)
return disposable
}
@ -66,7 +68,7 @@ fun <T1, T2, T3> observeNow(
observer: (T1, T2, T3) -> Unit,
): Disposable {
val disposable = CallbackObserver(c1, c2, c3) { observer(c1.value, c2.value, c3.value) }
// Call observer after observeChange to avoid double recomputation in most observables.
// Call observer after observeChange to avoid double recomputation in most cells.
observer(c1.value, c2.value, c3.value)
return disposable
}
@ -80,7 +82,7 @@ fun <T1, T2, T3, T4> observeNow(
): Disposable {
val disposable =
CallbackObserver(c1, c2, c3, c4) { observer(c1.value, c2.value, c3.value, c4.value) }
// Call observer after observeChange to avoid double recomputation in most observables.
// Call observer after observeChange to avoid double recomputation in most cells.
observer(c1.value, c2.value, c3.value, c4.value)
return disposable
}
@ -96,7 +98,7 @@ fun <T1, T2, T3, T4, T5> observeNow(
val disposable = CallbackObserver(c1, c2, c3, c4, c5) {
observer(c1.value, c2.value, c3.value, c4.value, c5.value)
}
// Call observer after observeChange to avoid double recomputation in most observables.
// Call observer after observeChange to avoid double recomputation in most cells.
observer(c1.value, c2.value, c3.value, c4.value, c5.value)
return disposable
}

View File

@ -1,9 +1,9 @@
package world.phantasmal.observable
package world.phantasmal.cell
typealias ChangeObserver<T> = (ChangeEvent<T>) -> Unit
open class ChangeEvent<out T>(
/** The observable's new value. */
/** The cell's new value. */
val value: T,
) {
operator fun component1() = value

View File

@ -1,6 +1,4 @@
package world.phantasmal.observable.cell
import world.phantasmal.observable.ChangeEvent
package world.phantasmal.cell
class DelegatingCell<T>(
private val getter: () -> T,

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable
package world.phantasmal.cell
interface Dependency<out T> {
// TODO: Docs.
@ -6,7 +6,7 @@ interface Dependency<out T> {
/**
* This method is not meant to be called from typical application code. Usually you'll want to
* use [Observable.observeChange].
* use [Cell.observeChange].
*/
fun addDependent(dependent: Dependent)

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable
package world.phantasmal.cell
interface Dependent {
/**

View File

@ -1,15 +1,10 @@
package world.phantasmal.observable.cell
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.observable.Observable
package world.phantasmal.cell
/**
* Cell of which the value depends on 0 or more dependencies.
*/
class DependentCell<T>(
private vararg val dependencies: Observable<*>,
private vararg val dependencies: Cell<*>,
private val compute: () -> T,
) : AbstractDependentCell<T, ChangeEvent<T>>() {

View File

@ -1,13 +1,10 @@
package world.phantasmal.observable.cell
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Observable
package world.phantasmal.cell
/**
* Similar to [DependentCell], except that this cell's [compute] returns a cell.
*/
class FlatteningDependentCell<T>(
vararg dependencies: Observable<*>,
vararg dependencies: Cell<*>,
compute: () -> Cell<T>,
) : AbstractFlatteningDependentCell<T, Cell<T>, ChangeEvent<T>>(dependencies, compute) {

View File

@ -1,11 +1,7 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.nopDisposable
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.ChangeObserver
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
class ImmutableCell<T>(override val value: T) : Dependency<T>, Cell<T> {
override val changeEvent: ChangeEvent<T>? get() = null

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import kotlin.reflect.KProperty

View File

@ -0,0 +1,18 @@
package world.phantasmal.cell
/**
* Defer propagation of changes to cells until the end of a code block. All changes to cells in a
* single mutation won't be propagated to their dependencies until the mutation is completed.
*/
inline fun mutate(block: () -> Unit) {
MutationManager.mutate(block)
}
/**
* Schedule a mutation to run right after the current mutation finishes. Can be used to change cells
* in an observer callback. This is usually a bad idea, but sometimes the situation where you have
* to change cells in response to other cells changing is very hard to avoid.
*/
fun mutateDeferred(block: () -> Unit) {
MutationManager.mutateDeferred(block)
}

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable
package world.phantasmal.cell
import kotlin.contracts.InvocationKind.EXACTLY_ONCE
import kotlin.contracts.contract
@ -53,7 +53,7 @@ object MutationManager {
}
fun dependencyStartedChanging() {
check(!dependencyChanging) { "An observable is already changing." }
check(!dependencyChanging) { "A cell is already changing." }
dependencyChanging = true
}

View File

@ -1,6 +1,4 @@
package world.phantasmal.observable.cell
import world.phantasmal.observable.ChangeEvent
package world.phantasmal.cell
class SimpleCell<T>(value: T) : AbstractCell<T>(), MutableCell<T> {
override var value: T = value

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.unsafe.unsafeAssertNotNull

View File

@ -1,7 +1,7 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.cell.Dependency
import world.phantasmal.cell.Dependent
abstract class AbstractFilteredListCell<E>(
protected val list: ListCell<E>,

View File

@ -1,12 +1,12 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.unsafe.unsafeAssertNotNull
import world.phantasmal.observable.CallbackChangeObserver
import world.phantasmal.observable.ChangeObserver
import world.phantasmal.observable.cell.AbstractCell
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.DependentCell
import world.phantasmal.cell.CallbackChangeObserver
import world.phantasmal.cell.ChangeObserver
import world.phantasmal.cell.AbstractCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.DependentCell
abstract class AbstractListCell<E> : AbstractCell<List<E>>(), ListCell<E> {

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
/**
* Simply delegates all methods to [backingList], even [equals], [hashCode] and [toString].

View File

@ -1,14 +1,14 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.observable.Observable
import world.phantasmal.cell.Dependency
import world.phantasmal.cell.Dependent
import world.phantasmal.cell.Cell
/**
* ListCell of which the value depends on 0 or more other observables.
* ListCell of which the value depends on 0 or more other cells.
*/
class DependentListCell<E>(
private vararg val dependencies: Observable<*>,
private vararg val dependencies: Cell<*>,
private val computeElements: () -> List<E>,
) : AbstractListCell<E>(), Dependent {
@ -35,12 +35,14 @@ class DependentListCell<E>(
_value = newElements
changeEvent = ListChangeEvent(
newElements,
listOf(ListChange(
index = 0,
prevSize = oldElements.size,
removed = oldElements,
inserted = newElements,
)),
listOf(
ListChange(
index = 0,
prevSize = oldElements.size,
removed = oldElements,
inserted = newElements,
)
),
)
valid = dependents.isNotEmpty()
}

View File

@ -1,12 +1,12 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.cell.Cell
import world.phantasmal.cell.ChangeEvent
import world.phantasmal.cell.Dependency
import world.phantasmal.cell.Dependent
import world.phantasmal.core.assert
import world.phantasmal.core.assertUnreachable
import world.phantasmal.core.unsafe.unsafeCast
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.observable.cell.Cell
class FilteredListCell<E>(
list: ListCell<E>,
@ -81,24 +81,28 @@ class FilteredListCell<E>(
mapping.index = insertionIndex
shift++
filteredChanges.add(ListChange(
insertionIndex,
prevSize,
removed = emptyList(),
inserted = listOf(element),
))
filteredChanges.add(
ListChange(
insertionIndex,
prevSize,
removed = emptyList(),
inserted = listOf(element),
)
)
} else {
val index = mapping.index + shift
val element = elements.removeAt(index)
mapping.index = -1
shift--
filteredChanges.add(ListChange(
index,
prevSize,
removed = listOf(element),
inserted = emptyList(),
))
filteredChanges.add(
ListChange(
index,
prevSize,
removed = listOf(element),
inserted = emptyList(),
)
)
}
} else if (oldResult) {
mapping.index += shift

View File

@ -1,19 +1,18 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.unsafe.unsafeAssertNotNull
import world.phantasmal.observable.CallbackChangeObserver
import world.phantasmal.observable.ChangeObserver
import world.phantasmal.observable.Observable
import world.phantasmal.observable.cell.AbstractFlatteningDependentCell
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.DependentCell
import world.phantasmal.cell.CallbackChangeObserver
import world.phantasmal.cell.ChangeObserver
import world.phantasmal.cell.AbstractFlatteningDependentCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.DependentCell
/**
* Similar to [DependentListCell], except that this cell's computeElements returns a [ListCell].
*/
class FlatteningDependentListCell<E>(
vararg dependencies: Observable<*>,
vararg dependencies: Cell<*>,
computeElements: () -> ListCell<E>,
) :
AbstractFlatteningDependentCell<List<E>, ListCell<E>, ListChangeEvent<E>>(
@ -64,12 +63,14 @@ class FlatteningDependentListCell<E>(
val old = oldValue ?: emptyList()
return ListChangeEvent(
newValue,
listOf(ListChange(
index = 0,
prevSize = old.size,
removed = old,
inserted = newValue,
)),
listOf(
ListChange(
index = 0,
prevSize = old.size,
removed = old,
inserted = newValue,
)
),
)
}
}

View File

@ -1,14 +1,14 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.nopDisposable
import world.phantasmal.observable.ChangeObserver
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.cell
import world.phantasmal.observable.cell.falseCell
import world.phantasmal.observable.cell.trueCell
import world.phantasmal.cell.ChangeObserver
import world.phantasmal.cell.Dependency
import world.phantasmal.cell.Dependent
import world.phantasmal.cell.Cell
import world.phantasmal.cell.cell
import world.phantasmal.cell.falseCell
import world.phantasmal.cell.trueCell
class ImmutableListCell<E>(private val elements: List<E>) : Dependency<List<E>>, ListCell<E> {
override val size: Cell<Int> = cell(elements.size)

View File

@ -1,7 +1,7 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.observable.cell.Cell
import world.phantasmal.cell.Cell
interface ListCell<out E> : Cell<List<E>> {
override val value: List<E>

View File

@ -1,9 +1,8 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.Observable
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.DependentCell
import world.phantasmal.observable.cell.ImmutableCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.DependentCell
import world.phantasmal.cell.ImmutableCell
private val EMPTY_LIST_CELL = ImmutableListCell<Nothing>(emptyList())
@ -21,13 +20,12 @@ fun <E> mutableListCell(vararg elements: E): MutableListCell<E> =
* Returns a cell that changes whenever this list cell is structurally changed or when its
* individual elements change.
*
* @param extractObservables Called on each element to determine which element changes should be
* observed.
* @param extractCells Called on each element to determine which element changes should be observed.
*/
fun <E> ListCell<E>.dependingOnElements(
extractObservables: (element: E) -> Array<out Observable<*>>,
extractCells: (element: E) -> Array<out Cell<*>>,
): Cell<List<E>> =
ListElementsDependentCell(this, extractObservables)
ListElementsDependentCell(this, extractCells)
fun <E, R> ListCell<E>.listMap(transform: (E) -> R): ListCell<R> =
DependentListCell(this) { value.map(transform) }

View File

@ -1,6 +1,6 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.cell.ChangeEvent
class ListChangeEvent<out E>(
value: List<E>,

View File

@ -1,20 +1,20 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.splice
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import world.phantasmal.observable.Observable
import world.phantasmal.observable.cell.AbstractCell
import world.phantasmal.cell.ChangeEvent
import world.phantasmal.cell.Dependency
import world.phantasmal.cell.Dependent
import world.phantasmal.cell.AbstractCell
import world.phantasmal.cell.Cell
/**
* Depends on a [ListCell] and zero or more observables per element in the list.
* Depends on a [ListCell] and zero or more cells per element in the list.
*/
class ListElementsDependentCell<E>(
private val list: ListCell<E>,
private val extractObservables: (element: E) -> Array<out Observable<*>>,
private val extractCells: (element: E) -> Array<out Cell<*>>,
) : AbstractCell<List<E>>(), Dependent {
/** An array of dependencies per [list] element, extracted by [extractObservables]. */
/** An array of dependencies per [list] element, extracted by [extractCells]. */
private val elementDependencies = mutableListOf<Array<out Dependency<*>>>()
private var valid = false
@ -46,7 +46,7 @@ class ListElementsDependentCell<E>(
}
}
val inserted = change.inserted.map(extractObservables)
val inserted = change.inserted.map(extractCells)
elementDependencies.splice(
startIndex = change.index,
@ -79,7 +79,7 @@ class ListElementsDependentCell<E>(
list.addDependent(this)
for (element in list.value) {
val dependencies = extractObservables(element)
val dependencies = extractCells(element)
for (dependency in dependencies) {
dependency.addDependent(this)

View File

@ -1,6 +1,6 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.MutableCell
import world.phantasmal.cell.MutableCell
interface MutableListCell<E> : ListCell<E>, MutableCell<List<E>> {
operator fun set(index: Int, element: E): E

View File

@ -1,8 +1,8 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.assertUnreachable
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.cell.Cell
import world.phantasmal.cell.Dependency
import world.phantasmal.cell.Cell
class SimpleFilteredListCell<E>(
list: ListCell<E>,

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.replaceAll

View File

@ -1,9 +1,9 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.*
class CellCreationTests : ObservableTestSuite {
class CellCreationTests : CellTestSuite {
@Test
fun test_cell() = test {
assertEquals(7, cell(7).value)

View File

@ -1,15 +1,17 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.core.disposable.use
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.ObservableTests
import kotlin.test.*
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertNull
/**
* Test suite for all [Cell] implementations. There is a subclass of this suite for every [Cell]
* implementation.
*/
interface CellTests : ObservableTests {
interface CellTests : DependencyTests {
override fun createProvider(): Provider
@Test
@ -17,17 +19,61 @@ interface CellTests : ObservableTests {
val p = createProvider()
// We literally just test that accessing the value property doesn't throw or return null.
assertNotNull(p.observable.value)
assertNotNull(p.cell.value)
}
@Test
fun value_is_accessible_with_observers() = test {
val p = createProvider()
disposer.add(p.observable.observeChange {})
disposer.add(p.cell.observeChange {})
// We literally just test that accessing the value property doesn't throw or return null.
assertNotNull(p.observable.value)
assertNotNull(p.cell.value)
}
@Test
fun calls_observers_when_events_are_emitted() = test {
val p = createProvider()
var changes = 0
disposer.add(
p.cell.observeChange {
changes++
}
)
p.emit()
assertEquals(1, changes)
p.emit()
p.emit()
p.emit()
assertEquals(4, changes)
}
@Test
fun does_not_call_observers_after_they_are_disposed() = test {
val p = createProvider()
var changes = 0
val observer = p.cell.observeChange {
changes++
}
p.emit()
assertEquals(1, changes)
observer.dispose()
p.emit()
p.emit()
p.emit()
assertEquals(1, changes)
}
@Test
@ -36,7 +82,7 @@ interface CellTests : ObservableTests {
var observedEvent: ChangeEvent<Any>? = null
disposer.add(p.observable.observeChange { changeEvent ->
disposer.add(p.cell.observeChange { changeEvent ->
observedEvent = changeEvent
})
@ -54,7 +100,7 @@ interface CellTests : ObservableTests {
var prevValue: Snapshot?
var observedValue: Snapshot? = null
disposer.add(p.observable.observeChange { changeEvent ->
disposer.add(p.cell.observeChange { changeEvent ->
assertNull(observedValue)
observedValue = changeEvent.value.snapshot()
})
@ -69,7 +115,7 @@ interface CellTests : ObservableTests {
// it should be equal to the cell's current value.
assertNotNull(observedValue)
assertNotEquals(prevValue, observedValue)
assertEquals(p.observable.value.snapshot(), observedValue)
assertEquals(p.cell.value.snapshot(), observedValue)
}
}
@ -86,16 +132,16 @@ interface CellTests : ObservableTests {
repeat(5) {
// Value should change after emit.
old = p.observable.value.snapshot()
old = p.cell.value.snapshot()
p.emit()
val new = p.observable.value.snapshot()
val new = p.cell.value.snapshot()
assertNotEquals(old, new)
// Value should not change when emit hasn't been called since the last access.
assertEquals(new, p.observable.value.snapshot())
assertEquals(new, p.cell.value.snapshot())
}
}
@ -108,7 +154,7 @@ interface CellTests : ObservableTests {
val p = createProvider()
var changes = 0
p.observable.observeNow {
p.cell.observeNow {
changes++
}.use {
p.emit()
@ -120,7 +166,7 @@ interface CellTests : ObservableTests {
@Test
fun propagates_changes_to_mapped_cell() = test {
val p = createProvider()
val mapped = p.observable.map { it.snapshot() }
val mapped = p.cell.map { it.snapshot() }
val initialValue = mapped.value
var observedValue: Snapshot? = null
@ -140,7 +186,7 @@ interface CellTests : ObservableTests {
fun propagates_changes_to_flat_mapped_cell() = test {
val p = createProvider()
val mapped = p.observable.flatMap { ImmutableCell(it.snapshot()) }
val mapped = p.cell.flatMap { ImmutableCell(it.snapshot()) }
val initialValue = mapped.value
var observedValue: Snapshot? = null
@ -156,8 +202,10 @@ interface CellTests : ObservableTests {
assertEquals(mapped.value, observedValue)
}
interface Provider : ObservableTests.Provider {
override val observable: Cell<Any>
interface Provider : DependencyTests.Provider {
val cell: Cell<Any>
override val dependency: Dependency<*> get() = cell
}
}

View File

@ -1,8 +1,5 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.Dependency
import world.phantasmal.observable.Dependent
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue

View File

@ -1,12 +1,11 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.observable.mutate
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFails
class ChangeTests : ObservableTestSuite {
class ChangeTests : CellTestSuite {
@Test
fun exceptions_during_a_change_set_are_allowed() = test {
val dependency = mutableCell(7)

View File

@ -1,13 +1,14 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
@Suppress("unused")
class DelegatingCellTests : RegularCellTests, MutableCellTests<Int> {
override fun createProvider() = object : MutableCellTests.Provider<Int> {
private var v = 17
override val observable = DelegatingCell({ v }, { v = it })
override val cell = DelegatingCell({ v }, { v = it })
override fun emit() {
observable.value += 2
cell.value += 2
}
override fun createValue(): Int = v + 1

View File

@ -1,9 +1,9 @@
package world.phantasmal.observable
package world.phantasmal.cell
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.*
interface DependencyTests : ObservableTestSuite {
interface DependencyTests : CellTestSuite {
fun createProvider(): Provider
@Test

View File

@ -1,5 +1,6 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
@Suppress("unused")
class DependentCellTests : RegularCellTests, CellWithDependenciesTests {
override fun createProvider() = Provider()
@ -20,7 +21,7 @@ class DependentCellTests : RegularCellTests, CellWithDependenciesTests {
class Provider : CellTests.Provider {
private val dependencyCell = SimpleCell(1)
override val observable = DependentCell(dependencyCell) { 2 * dependencyCell.value }
override val cell = DependentCell(dependencyCell) { 2 * dependencyCell.value }
override fun emit() {
dependencyCell.value += 2

View File

@ -1,14 +1,15 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.observable.cell.list.SimpleListCell
import world.phantasmal.cell.list.SimpleListCell
@Suppress("unused")
class DependentCellWithSimpleListCellTests : CellTests {
override fun createProvider() = Provider()
class Provider : CellTests.Provider {
private val dependencyCell = SimpleListCell(mutableListOf("a", "b", "c"))
override val observable = DependentCell(dependencyCell) { dependencyCell.value }
override val cell = DependentCell(dependencyCell) { dependencyCell.value }
override fun emit() {
dependencyCell.add("x")

View File

@ -1,9 +1,10 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
/**
* In these tests both the direct dependency and the transitive dependency of the
* [FlatteningDependentCell] change.
*/
@Suppress("unused")
class FlatteningDependentCellDirectAndTransitiveDependencyEmitTests : CellTests {
override fun createProvider() = Provider()
@ -11,7 +12,7 @@ class FlatteningDependentCellDirectAndTransitiveDependencyEmitTests : CellTests
// This cell is both the direct and transitive dependency.
private val dependencyCell = SimpleCell('a')
override val observable = FlatteningDependentCell(dependencyCell) { dependencyCell }
override val cell = FlatteningDependentCell(dependencyCell) { dependencyCell }
override fun emit() {
dependencyCell.value += 1

View File

@ -1,8 +1,9 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
/**
* In these tests the direct dependency of the [FlatteningDependentCell] changes.
*/
@Suppress("unused")
class FlatteningDependentCellDirectDependencyEmitsTests : RegularCellTests {
override fun createProvider() = object : CellTests.Provider {
// The transitive dependency can't change.
@ -11,7 +12,7 @@ class FlatteningDependentCellDirectDependencyEmitsTests : RegularCellTests {
// The direct dependency of the cell under test can change.
val directDependency = SimpleCell(transitiveDependency)
override val observable =
override val cell =
FlatteningDependentCell(directDependency) { directDependency.value }
override fun emit() {

View File

@ -1,8 +1,9 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
/**
* In these tests the dependency of the [FlatteningDependentCell]'s direct dependency changes.
*/
@Suppress("unused")
class FlatteningDependentCellTransitiveDependencyEmitsTests :
RegularCellTests,
CellWithDependenciesTests {
@ -30,7 +31,7 @@ class FlatteningDependentCellTransitiveDependencyEmitsTests :
// The direct dependency of the cell under test can't change.
private val directDependency = ImmutableCell(transitiveDependency)
override val observable =
override val cell =
FlatteningDependentCell(directDependency) { directDependency.value }
override fun emit() {

View File

@ -1,11 +1,11 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.cell.test.CellTestSuite
import world.phantasmal.core.disposable.DisposableTracking
import world.phantasmal.observable.test.ObservableTestSuite
import kotlin.test.Test
import kotlin.test.assertEquals
class ImmutableCellTests : ObservableTestSuite {
class ImmutableCellTests : CellTestSuite {
/**
* As an optimization we simply ignore any observers and return a singleton Nop disposable.
*/

View File

@ -1,6 +1,5 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import world.phantasmal.observable.Dependent
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
@ -14,15 +13,15 @@ interface MutableCellTests<T : Any> : CellTests {
var observedValue: Any? = null
disposer.add(p.observable.observeChange {
disposer.add(p.cell.observeChange {
assertNull(observedValue)
observedValue = it.value
})
val newValue = p.createValue()
p.observable.value = newValue
p.cell.value = newValue
assertEquals(newValue, p.observable.value)
assertEquals(newValue, p.cell.value)
assertEquals(newValue, observedValue)
}
@ -139,11 +138,11 @@ interface MutableCellTests<T : Any> : CellTests {
// }
interface Provider<T : Any> : CellTests.Provider {
override val observable: MutableCell<T>
override val cell: MutableCell<T>
/**
* Returns a value that can be assigned to [observable] and that's different from
* [observable]'s current and all previous values.
* Returns a value that can be assigned to [cell] and that's different from
* [cell]'s current and all previous values.
*/
fun createValue(): T
}

View File

@ -1,11 +1,10 @@
package world.phantasmal.observable
package world.phantasmal.cell
import world.phantasmal.observable.cell.mutableCell
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.Test
import kotlin.test.assertEquals
class MutationTests : ObservableTestSuite {
class MutationTests : CellTestSuite {
@Test
fun can_change_observed_cell_with_mutateDeferred() = test {
val cell = mutableCell(0)

View File

@ -1,4 +1,4 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
import kotlin.test.Test
import kotlin.test.assertEquals

View File

@ -1,14 +1,15 @@
package world.phantasmal.observable.cell
package world.phantasmal.cell
@Suppress("unused")
class SimpleCellTests : RegularCellTests, MutableCellTests<Int> {
override fun createProvider() = object : MutableCellTests.Provider<Int> {
override val observable = SimpleCell(1)
override val cell = SimpleCell(1)
override fun emit() {
observable.value += 2
cell.value += 2
}
override fun createValue(): Int = observable.value + 1
override fun createValue(): Int = cell.value + 1
}
override fun <T> createWithValue(value: T) = SimpleCell(value)

View File

@ -1,8 +1,9 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.CellWithDependenciesTests
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellWithDependenciesTests
@Suppress("unused")
class DependentListCellTests : ListCellTests, CellWithDependenciesTests {
override fun createProvider() = createListProvider(empty = true)
@ -21,7 +22,7 @@ class DependentListCellTests : ListCellTests, CellWithDependenciesTests {
private val dependencyCell =
SimpleListCell(if (empty) mutableListOf() else mutableListOf(5))
override val observable =
override val cell =
DependentListCell(dependencyCell) { dependencyCell.value.map { 2 * it } }
override fun addElement() {

View File

@ -1,13 +1,17 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.*
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellWithDependenciesTests
import world.phantasmal.cell.cell
import world.phantasmal.cell.map
@Suppress("unused")
class FilteredListCellListDependencyEmitsTests : ListCellTests, CellWithDependenciesTests {
override fun createListProvider(empty: Boolean) = object : ListCellTests.Provider {
private val dependencyCell =
SimpleListCell(if (empty) mutableListOf(5) else mutableListOf(5, 10))
override val observable =
override val cell =
FilteredListCell(
list = dependencyCell,
predicate = cell { cell(it % 2 == 0) },

View File

@ -1,12 +1,13 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.cell
import world.phantasmal.observable.cell.map
import world.phantasmal.cell.Cell
import world.phantasmal.cell.cell
import world.phantasmal.cell.map
// TODO: A test suite that tests FilteredListCell while its predicate dependency is changing.
// TODO: A test suite that tests FilteredListCell while the predicate results are changing.
// TODO: A test suite that tests FilteredListCell while all 3 types of dependencies are changing.
@Suppress("unused")
class FilteredListCellTests : SuperFilteredListCellTests {
override fun <E> createFilteredListCell(list: ListCell<E>, predicate: Cell<(E) -> Boolean>) =
FilteredListCell(list, predicate.map { p -> { cell(p(it)) } })

View File

@ -1,10 +1,11 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.SimpleCell
import world.phantasmal.cell.SimpleCell
/**
* In these tests the direct dependency of the [FlatteningDependentListCell] changes.
*/
@Suppress("unused")
class FlatteningDependentListCellDirectDependencyEmitsTests : ListCellTests {
override fun createListProvider(empty: Boolean) = object : ListCellTests.Provider {
// The transitive dependency can't change.
@ -13,7 +14,7 @@ class FlatteningDependentListCellDirectDependencyEmitsTests : ListCellTests {
// The direct dependency of the list under test can change.
private val directDependency = SimpleCell<ListCell<Int>>(transitiveDependency)
override val observable =
override val cell =
FlatteningDependentListCell(directDependency) { directDependency.value }
override fun addElement() {

View File

@ -1,13 +1,14 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.CellTests
import world.phantasmal.observable.cell.CellWithDependenciesTests
import world.phantasmal.observable.cell.ImmutableCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellTests
import world.phantasmal.cell.CellWithDependenciesTests
import world.phantasmal.cell.ImmutableCell
/**
* In these tests the dependency of the [FlatteningDependentListCell]'s direct dependency changes.
*/
@Suppress("unused")
class FlatteningDependentListCellTransitiveDependencyEmitsTests :
ListCellTests,
CellWithDependenciesTests {
@ -33,7 +34,7 @@ class FlatteningDependentListCellTransitiveDependencyEmitsTests :
// The direct dependency of the list under test can't change.
private val directDependency = ImmutableCell<ListCell<Int>>(transitiveDependency)
override val observable =
override val cell =
FlatteningDependentListCell(directDependency) { directDependency.value }
override fun addElement() {

View File

@ -1,12 +1,12 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.core.disposable.DisposableTracking
import world.phantasmal.observable.cell.observeNow
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.observeNow
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.Test
import kotlin.test.assertEquals
class ImmutableListCellTests : ObservableTestSuite {
class ImmutableListCellTests : CellTestSuite {
/**
* As an optimization we simply ignore any observers and return a singleton Nop disposable.
*/

View File

@ -0,0 +1,17 @@
package world.phantasmal.cell.list
import world.phantasmal.cell.SimpleCell
import world.phantasmal.cell.test.CellTestSuite
import world.phantasmal.cell.test.assertListCellEquals
import kotlin.test.Test
class ListCellCreationTests : CellTestSuite {
@Test
fun test_flatMapToList() = test {
val cell = SimpleCell(SimpleListCell(mutableListOf(1, 2, 3, 4, 5)))
val mapped = cell.flatMapToList { it }
assertListCellEquals(listOf(1, 2, 3, 4, 5), mapped)
}
}

View File

@ -1,6 +1,6 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.CellTests
import world.phantasmal.cell.CellTests
import kotlin.test.*
/**
@ -18,18 +18,18 @@ interface ListCellTests : CellTests {
// We literally just test that accessing the value property doesn't throw or return the
// wrong list.
assertTrue(p.observable.value.isNotEmpty())
assertTrue(p.cell.value.isNotEmpty())
}
@Test
fun list_value_is_accessible_with_observers() = test {
val p = createListProvider(empty = false)
disposer.add(p.observable.observeListChange {})
disposer.add(p.cell.observeListChange {})
// We literally just test that accessing the value property doesn't throw or return the
// wrong list.
assertTrue(p.observable.value.isNotEmpty())
assertTrue(p.cell.value.isNotEmpty())
}
@Test
@ -38,7 +38,7 @@ interface ListCellTests : CellTests {
var observedEvent: ListChangeEvent<Any>? = null
disposer.add(p.observable.observeListChange { listChangeEvent ->
disposer.add(p.cell.observeListChange { listChangeEvent ->
observedEvent = listChangeEvent
})
@ -56,7 +56,7 @@ interface ListCellTests : CellTests {
var event: ListChangeEvent<*>? = null
disposer.add(
p.observable.observeListChange {
p.cell.observeListChange {
assertNull(event)
event = it
}
@ -75,12 +75,12 @@ interface ListCellTests : CellTests {
fun updates_size_correctly() = test {
val p = createProvider()
assertEquals(0, p.observable.size.value)
assertEquals(0, p.cell.size.value)
var observedSize: Int? = null
disposer.add(
p.observable.size.observeChange {
p.cell.size.observeChange {
assertNull(observedSize)
observedSize = it.value
}
@ -91,7 +91,7 @@ interface ListCellTests : CellTests {
p.addElement()
assertEquals(i, p.observable.size.value)
assertEquals(i, p.cell.size.value)
assertEquals(i, observedSize)
}
}
@ -101,20 +101,20 @@ interface ListCellTests : CellTests {
val p = createProvider()
assertFailsWith(IndexOutOfBoundsException::class) {
p.observable[0]
p.cell[0]
}
p.addElement()
// Shouldn't throw at this point.
p.observable[0]
p.cell[0]
}
@Test
fun fold() = test {
val p = createProvider()
val fold = p.observable.fold(0) { acc, _ -> acc + 1 }
val fold = p.cell.fold(0) { acc, _ -> acc + 1 }
var observedValue: Int? = null
@ -139,7 +139,7 @@ interface ListCellTests : CellTests {
fun sumBy() = test {
val p = createProvider()
val sum = p.observable.sumOf { 1 }
val sum = p.cell.sumOf { 1 }
var observedValue: Int? = null
@ -164,7 +164,7 @@ interface ListCellTests : CellTests {
fun filtered() = test {
val p = createProvider()
val filtered = p.observable.filtered { true }
val filtered = p.cell.filtered { true }
var event: ListChangeEvent<*>? = null
@ -189,7 +189,7 @@ interface ListCellTests : CellTests {
fun firstOrNull() = test {
val p = createProvider()
val firstOrNull = p.observable.firstOrNull()
val firstOrNull = p.cell.firstOrNull()
var observedValue: Any? = null
@ -217,7 +217,7 @@ interface ListCellTests : CellTests {
}
interface Provider : CellTests.Provider {
override val observable: ListCell<Any>
override val cell: ListCell<Any>
/**
* Adds an element to the [ListCell] under test.

View File

@ -1,6 +1,11 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.*
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellTests
import world.phantasmal.cell.CellWithDependenciesTests
import world.phantasmal.cell.DependentCell
import world.phantasmal.cell.MutableCell
import world.phantasmal.cell.SimpleCell
/**
* In these tests, the direct list cell dependency of the [ListElementsDependentCell] doesn't
@ -16,7 +21,7 @@ class ListElementsDependentCellElementEmitsTests : CellWithDependenciesTests {
private val directDependency: ListCell<Element> =
ImmutableListCell(listOf(Element(1), transitiveDependency, Element(3)))
override val observable =
override val cell =
ListElementsDependentCell(directDependency) { arrayOf(it.int, it.double, it.string) }
override fun emit() {

View File

@ -1,8 +1,8 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.CellTests
import world.phantasmal.observable.cell.ImmutableCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellTests
import world.phantasmal.cell.ImmutableCell
/**
* In these tests, the direct list cell dependency of the [ListElementsDependentCell] changes, while
@ -15,7 +15,7 @@ class ListElementsDependentCellListCellEmitsTests : CellTests {
private val directDependency: SimpleListCell<Cell<Int>> =
SimpleListCell(mutableListOf(ImmutableCell(1), ImmutableCell(2), ImmutableCell(3)))
override val observable =
override val cell =
ListElementsDependentCell(directDependency) { arrayOf(it) }
override fun emit() {

View File

@ -1,8 +1,8 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.ChangeEvent
import world.phantasmal.observable.cell.SimpleCell
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.ChangeEvent
import world.phantasmal.cell.SimpleCell
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.Test
import kotlin.test.assertNotNull
import kotlin.test.assertNull
@ -11,7 +11,7 @@ import kotlin.test.assertNull
* Standard tests are done by [ListElementsDependentCellElementEmitsTests] and
* [ListElementsDependentCellListCellEmitsTests].
*/
class ListElementsDependentCellTests : ObservableTestSuite {
class ListElementsDependentCellTests : CellTestSuite {
@Test
fun element_changes_are_correctly_propagated() = test {
val list = SimpleListCell(

View File

@ -1,6 +1,6 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.MutableCellTests
import world.phantasmal.cell.MutableCellTests
import kotlin.test.*
/**
@ -16,18 +16,18 @@ interface MutableListCellTests<T : Any> : ListCellTests, MutableCellTests<List<T
var changeEvent: ListChangeEvent<T>? = null
disposer.add(p.observable.observeListChange {
disposer.add(p.cell.observeListChange {
assertNull(changeEvent)
changeEvent = it
})
// Insert once.
val v1 = p.createElement()
p.observable.add(v1)
p.cell.add(v1)
run {
assertEquals(1, p.observable.size.value)
assertEquals(v1, p.observable[0])
assertEquals(1, p.cell.size.value)
assertEquals(v1, p.cell[0])
val e = changeEvent
assertNotNull(e)
@ -44,12 +44,12 @@ interface MutableListCellTests<T : Any> : ListCellTests, MutableCellTests<List<T
changeEvent = null
val v2 = p.createElement()
p.observable.add(v2)
p.cell.add(v2)
run {
assertEquals(2, p.observable.size.value)
assertEquals(v1, p.observable[0])
assertEquals(v2, p.observable[1])
assertEquals(2, p.cell.size.value)
assertEquals(v1, p.cell[0])
assertEquals(v2, p.cell[1])
val e = changeEvent
assertNotNull(e)
@ -66,13 +66,13 @@ interface MutableListCellTests<T : Any> : ListCellTests, MutableCellTests<List<T
changeEvent = null
val v3 = p.createElement()
p.observable.add(1, v3)
p.cell.add(1, v3)
run {
assertEquals(3, p.observable.size.value)
assertEquals(v1, p.observable[0])
assertEquals(v3, p.observable[1])
assertEquals(v2, p.observable[2])
assertEquals(3, p.cell.size.value)
assertEquals(v1, p.cell[0])
assertEquals(v3, p.cell[1])
assertEquals(v2, p.cell[2])
val e = changeEvent
assertNotNull(e)
@ -87,7 +87,7 @@ interface MutableListCellTests<T : Any> : ListCellTests, MutableCellTests<List<T
}
interface Provider<T : Any> : ListCellTests.Provider, MutableCellTests.Provider<List<T>> {
override val observable: MutableListCell<T>
override val cell: MutableListCell<T>
fun createElement(): T
}

View File

@ -1,13 +1,14 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.CellWithDependenciesTests
import world.phantasmal.observable.cell.cell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellWithDependenciesTests
import world.phantasmal.cell.cell
/**
* In these tests the list dependency of the [SimpleListCell] changes and the predicate
* dependency does not.
*/
@Suppress("unused")
class SimpleFilteredListCellListDependencyEmitsTests :
ListCellTests, CellWithDependenciesTests {
@ -15,7 +16,7 @@ class SimpleFilteredListCellListDependencyEmitsTests :
private val dependencyCell =
SimpleListCell(if (empty) mutableListOf(5) else mutableListOf(5, 10))
override val observable = SimpleFilteredListCell(
override val cell = SimpleFilteredListCell(
list = dependencyCell,
predicate = cell { it % 2 == 0 },
)

View File

@ -1,14 +1,15 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.CellWithDependenciesTests
import world.phantasmal.observable.cell.SimpleCell
import world.phantasmal.observable.cell.map
import world.phantasmal.cell.Cell
import world.phantasmal.cell.CellWithDependenciesTests
import world.phantasmal.cell.SimpleCell
import world.phantasmal.cell.map
/**
* In these tests the predicate dependency of the [SimpleListCell] changes and the list dependency
* does not.
*/
@Suppress("unused")
class SimpleFilteredListCellPredicateDependencyEmitsTests :
ListCellTests, CellWithDependenciesTests {
@ -16,7 +17,7 @@ class SimpleFilteredListCellPredicateDependencyEmitsTests :
private var maxValue = if (empty) 0 else 1
private val predicateCell = SimpleCell<(Int) -> Boolean> { it <= maxValue }
override val observable = SimpleFilteredListCell(
override val cell = SimpleFilteredListCell(
list = ImmutableListCell((1..20).toList()),
predicate = predicateCell,
)

View File

@ -1,9 +1,10 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.cell.Cell
// TODO: A test suite that tests SimpleFilteredListCell while both types of dependencies are
// changing.
@Suppress("unused")
class SimpleFilteredListCellTests : SuperFilteredListCellTests {
override fun <E> createFilteredListCell(list: ListCell<E>, predicate: Cell<(E) -> Boolean>) =
SimpleFilteredListCell(list, predicate)

View File

@ -1,6 +1,6 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.test.assertListCellEquals
import world.phantasmal.cell.test.assertListCellEquals
import kotlin.test.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
@ -12,10 +12,10 @@ class SimpleListCellTests : MutableListCellTests<Int> {
override fun createListProvider(empty: Boolean) = object : MutableListCellTests.Provider<Int> {
private var nextElement = 0
override val observable = SimpleListCell(if (empty) mutableListOf() else mutableListOf(-13))
override val cell = SimpleListCell(if (empty) mutableListOf() else mutableListOf(-13))
override fun addElement() {
observable.add(createElement())
cell.add(createElement())
}
override fun createValue(): List<Int> = listOf(createElement())

View File

@ -1,15 +1,15 @@
package world.phantasmal.observable.cell.list
package world.phantasmal.cell.list
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.ImmutableCell
import world.phantasmal.observable.cell.SimpleCell
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.cell.Cell
import world.phantasmal.cell.ImmutableCell
import world.phantasmal.cell.SimpleCell
import world.phantasmal.cell.test.CellTestSuite
import kotlin.test.*
/**
* Tests that apply to all filtered list implementations.
*/
interface SuperFilteredListCellTests : ObservableTestSuite {
interface SuperFilteredListCellTests : CellTestSuite {
fun <E> createFilteredListCell(list: ListCell<E>, predicate: Cell<(E) -> Boolean>): ListCell<E>
@Test
@ -172,12 +172,14 @@ interface SuperFilteredListCellTests : ObservableTestSuite {
val changes: MutableList<ListChange<Int>> = mutableListOf()
for (newElement in newElements) {
changes.add(ListChange(
changes.add(
ListChange(
index = elements.size,
prevSize = elements.size,
removed = emptyList(),
inserted = listOf(newElement),
))
)
)
elements.add(newElement)
}

View File

@ -1,9 +1,9 @@
package world.phantasmal.observable.test
package world.phantasmal.cell.test
import world.phantasmal.core.disposable.Disposer
import world.phantasmal.testUtils.AbstractTestSuite
import world.phantasmal.testUtils.TestContext
interface ObservableTestSuite : AbstractTestSuite<TestContext> {
interface CellTestSuite : AbstractTestSuite<TestContext> {
override fun createContext(disposer: Disposer) = TestContext(disposer)
}

View File

@ -1,6 +1,6 @@
package world.phantasmal.observable.test
package world.phantasmal.cell.test
import world.phantasmal.observable.cell.list.ListCell
import world.phantasmal.cell.list.ListCell
import kotlin.test.assertEquals
fun <E> assertListCellEquals(expected: List<E>, actual: ListCell<E>) {

View File

@ -1,5 +0,0 @@
package world.phantasmal.observable
interface Emitter<T> : Observable<T> {
fun emit(event: ChangeEvent<T>)
}

View File

@ -1,19 +0,0 @@
package world.phantasmal.observable
/**
* Defer propagation of changes to observables until the end of a code block. All changes to
* observables in a single mutation won't be propagated to their dependencies until the mutation is
* completed.
*/
inline fun mutate(block: () -> Unit) {
MutationManager.mutate(block)
}
/**
* Schedule a mutation to run right after the current mutation finishes. You can use this to change
* observables in an observer callback. This is usually a bad idea, but sometimes the situation
* where you have to change observables in response to observables changing is very hard to avoid.
*/
fun mutateDeferred(block: () -> Unit) {
MutationManager.mutateDeferred(block)
}

View File

@ -1,10 +0,0 @@
package world.phantasmal.observable
import world.phantasmal.core.disposable.Disposable
interface Observable<out T> : Dependency<T> {
/**
* [observer] will be called whenever this observable changes.
*/
fun observeChange(observer: ChangeObserver<T>): Disposable
}

View File

@ -1,8 +0,0 @@
package world.phantasmal.observable
import world.phantasmal.core.disposable.Disposable
fun <T> emitter(): Emitter<T> = SimpleEmitter()
fun <T> Observable<T>.observe(observer: (T) -> Unit): Disposable =
observeChange { observer(it.value) }

View File

@ -1,19 +0,0 @@
package world.phantasmal.observable
import world.phantasmal.core.disposable.Disposable
// TODO: Should multiple events be emitted somehow during a change set? At the moment no application
// code seems to care.
class SimpleEmitter<T> : AbstractDependency<T>(), Emitter<T> {
override var changeEvent: ChangeEvent<T>? = null
private set
override fun emit(event: ChangeEvent<T>) {
applyChange {
this.changeEvent = event
}
}
override fun observeChange(observer: ChangeObserver<T>): Disposable =
CallbackChangeObserver(this, observer)
}

View File

@ -1,13 +0,0 @@
package world.phantasmal.observable.cell
import world.phantasmal.observable.Observable
import kotlin.reflect.KProperty
/**
* An observable with the notion of a current [value].
*/
interface Cell<out T> : Observable<T> {
val value: T
operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
}

View File

@ -1,62 +0,0 @@
package world.phantasmal.observable
import kotlin.test.Test
import kotlin.test.assertEquals
/**
* Test suite for all [Observable] implementations. There is a subclass of this suite for every
* [Observable] implementation.
*/
interface ObservableTests : DependencyTests {
override fun createProvider(): Provider
@Test
fun calls_observers_when_events_are_emitted() = test {
val p = createProvider()
var changes = 0
disposer.add(
p.observable.observeChange {
changes++
}
)
p.emit()
assertEquals(1, changes)
p.emit()
p.emit()
p.emit()
assertEquals(4, changes)
}
@Test
fun does_not_call_observers_after_they_are_disposed() = test {
val p = createProvider()
var changes = 0
val observer = p.observable.observeChange {
changes++
}
p.emit()
assertEquals(1, changes)
observer.dispose()
p.emit()
p.emit()
p.emit()
assertEquals(1, changes)
}
interface Provider : DependencyTests.Provider {
val observable: Observable<*>
override val dependency: Dependency<*> get() = observable
}
}

View File

@ -1,11 +0,0 @@
package world.phantasmal.observable
class SimpleEmitterTests : ObservableTests {
override fun createProvider() = object : ObservableTests.Provider {
override val observable = SimpleEmitter<Any>()
override fun emit() {
observable.emit(ChangeEvent(Any()))
}
}
}

View File

@ -1,17 +0,0 @@
package world.phantasmal.observable.cell.list
import world.phantasmal.observable.cell.SimpleCell
import world.phantasmal.observable.test.ObservableTestSuite
import world.phantasmal.observable.test.assertListCellEquals
import kotlin.test.Test
class ListCellCreationTests : ObservableTestSuite {
@Test
fun test_flatMapToList() = test {
val cell = SimpleCell(SimpleListCell(mutableListOf(1, 2, 3, 4, 5)))
val mapped = cell.flatMapToList { it }
assertListCellEquals(listOf(1, 2, 3, 4, 5), mapped)
}
}

View File

@ -3,7 +3,7 @@ rootProject.name = "phantasmal-world"
include(
":core",
":psolib",
":observable",
":cell",
":psoserv",
":test-utils",
":web",

View File

@ -46,7 +46,7 @@ controller has no knowledge of the GUI layer.
### models
The models package contains observable model objects. Models expose read-only observable properties
The models package contains observable model objects. Models expose read-only cell properties
and allow their properties to be changed via setters which validate their inputs.
### stores

View File

@ -1,6 +1,6 @@
package world.phantasmal.web.application.controllers
import world.phantasmal.observable.cell.Cell
import world.phantasmal.cell.Cell
import world.phantasmal.web.core.PwToolType
import world.phantasmal.web.core.stores.UiStore
import world.phantasmal.webui.controllers.Controller

View File

@ -4,8 +4,8 @@ import kotlinx.browser.window
import kotlinx.datetime.Clock
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.mutableCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.mutableCell
import world.phantasmal.web.core.PwToolType
import world.phantasmal.web.core.stores.UiStore
import world.phantasmal.webui.controllers.Controller

View File

@ -1,9 +1,9 @@
package world.phantasmal.web.application.widgets
import org.w3c.dom.Node
import world.phantasmal.observable.cell.cell
import world.phantasmal.observable.cell.falseCell
import world.phantasmal.observable.cell.list.listCell
import world.phantasmal.cell.cell
import world.phantasmal.cell.falseCell
import world.phantasmal.cell.list.listCell
import world.phantasmal.web.application.controllers.NavigationController
import world.phantasmal.web.core.dom.externalLink
import world.phantasmal.web.core.models.Server

View File

@ -1,9 +1,9 @@
package world.phantasmal.web.application.widgets
import org.w3c.dom.Node
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.nullCell
import world.phantasmal.observable.cell.trueCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.nullCell
import world.phantasmal.cell.trueCell
import world.phantasmal.web.core.PwToolType
import world.phantasmal.webui.dom.input
import world.phantasmal.webui.dom.label

View File

@ -1,8 +1,8 @@
package world.phantasmal.web.core.controllers
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.map
import world.phantasmal.observable.mutateDeferred
import world.phantasmal.cell.Cell
import world.phantasmal.cell.map
import world.phantasmal.cell.mutateDeferred
import world.phantasmal.web.core.PwToolType
import world.phantasmal.web.core.stores.UiStore
import world.phantasmal.webui.controllers.Tab

View File

@ -0,0 +1,19 @@
package world.phantasmal.web.core.observable
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.disposable
class Emitter<T> : Observable<T> {
private val observers: MutableList<(T) -> Unit> = mutableListOf()
fun emit(event: T) {
for (observer in observers) {
observer(event)
}
}
override fun observe(observer: (T) -> Unit): Disposable {
observers.add(observer)
return disposable { observers.remove(observer) }
}
}

View File

@ -0,0 +1,10 @@
package world.phantasmal.web.core.observable
import world.phantasmal.core.disposable.Disposable
interface Observable<out T> {
/**
* [observer] will be called whenever this observable changes.
*/
fun observe(observer: (T) -> Unit): Disposable
}

View File

@ -0,0 +1,3 @@
package world.phantasmal.web.core.observable
fun <T> emitter(): Emitter<T> = Emitter()

View File

@ -6,9 +6,9 @@ import org.w3c.dom.events.KeyboardEvent
import world.phantasmal.core.disposable.Disposable
import world.phantasmal.core.disposable.TrackedDisposable
import world.phantasmal.core.disposable.disposable
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.eq
import world.phantasmal.observable.cell.mutableCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.eq
import world.phantasmal.cell.mutableCell
import world.phantasmal.web.core.PwToolType
import world.phantasmal.web.core.models.Server
import world.phantasmal.webui.DisposableContainer

View File

@ -1,6 +1,6 @@
package world.phantasmal.web.core.undo
import world.phantasmal.observable.cell.Cell
import world.phantasmal.cell.Cell
import world.phantasmal.web.core.commands.Command
interface Undo {

View File

@ -1,8 +1,15 @@
package world.phantasmal.web.core.undo
import world.phantasmal.observable.cell.*
import world.phantasmal.observable.cell.list.fold
import world.phantasmal.observable.cell.list.mutableListCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.and
import world.phantasmal.cell.falseCell
import world.phantasmal.cell.flatMap
import world.phantasmal.cell.flatten
import world.phantasmal.cell.list.fold
import world.phantasmal.cell.list.mutableListCell
import world.phantasmal.cell.mutableCell
import world.phantasmal.cell.nullCell
import world.phantasmal.cell.trueCell
import world.phantasmal.web.core.commands.Command
class UndoManager {

View File

@ -1,7 +1,11 @@
package world.phantasmal.web.core.undo
import world.phantasmal.observable.cell.*
import world.phantasmal.observable.cell.list.mutableListCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.eq
import world.phantasmal.cell.gt
import world.phantasmal.cell.list.mutableListCell
import world.phantasmal.cell.map
import world.phantasmal.cell.mutableCell
import world.phantasmal.web.core.commands.Command
/**

View File

@ -3,8 +3,8 @@ package world.phantasmal.web.core.widgets
import kotlinx.coroutines.launch
import mu.KotlinLogging
import org.w3c.dom.Node
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.trueCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.trueCell
import world.phantasmal.web.core.controllers.*
import world.phantasmal.web.externals.goldenLayout.GoldenLayout
import world.phantasmal.webui.dom.div

View File

@ -1,9 +1,9 @@
package world.phantasmal.web.core.widgets
import org.w3c.dom.Node
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.falseCell
import world.phantasmal.observable.cell.trueCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.falseCell
import world.phantasmal.cell.trueCell
import world.phantasmal.webui.dom.div
import world.phantasmal.webui.widgets.Label
import world.phantasmal.webui.widgets.Widget

View File

@ -1,7 +1,11 @@
package world.phantasmal.web.huntOptimizer.controllers
import world.phantasmal.observable.cell.list.*
import world.phantasmal.observable.cell.mutableCell
import world.phantasmal.cell.mutableCell
import world.phantasmal.cell.list.ListCell
import world.phantasmal.cell.list.dependingOnElements
import world.phantasmal.cell.list.filtered
import world.phantasmal.cell.list.listCell
import world.phantasmal.cell.list.mapToList
import world.phantasmal.psolib.Episode
import world.phantasmal.psolib.fileFormats.quest.NpcType
import world.phantasmal.web.huntOptimizer.models.HuntMethodModel

View File

@ -1,8 +1,8 @@
package world.phantasmal.web.huntOptimizer.controllers
import world.phantasmal.observable.cell.cell
import world.phantasmal.observable.cell.list.ListCell
import world.phantasmal.observable.cell.list.mapToList
import world.phantasmal.cell.cell
import world.phantasmal.cell.list.ListCell
import world.phantasmal.cell.list.mapToList
import world.phantasmal.web.huntOptimizer.models.OptimalMethodModel
import world.phantasmal.web.huntOptimizer.stores.HuntOptimizerStore
import world.phantasmal.webui.controllers.Column

View File

@ -1,10 +1,10 @@
package world.phantasmal.web.huntOptimizer.controllers
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.MutableCell
import world.phantasmal.observable.cell.list.ListCell
import world.phantasmal.observable.cell.list.filtered
import world.phantasmal.observable.cell.mutableCell
import world.phantasmal.cell.Cell
import world.phantasmal.cell.MutableCell
import world.phantasmal.cell.list.ListCell
import world.phantasmal.cell.list.filtered
import world.phantasmal.cell.mutableCell
import world.phantasmal.web.huntOptimizer.models.WantedItemModel
import world.phantasmal.web.huntOptimizer.stores.HuntOptimizerStore
import world.phantasmal.web.shared.dto.ItemType

View File

@ -2,9 +2,9 @@ package world.phantasmal.web.huntOptimizer.models
import world.phantasmal.psolib.Episode
import world.phantasmal.psolib.fileFormats.quest.NpcType
import world.phantasmal.observable.cell.Cell
import world.phantasmal.observable.cell.mutableCell
import world.phantasmal.observable.cell.orElse
import world.phantasmal.cell.Cell
import world.phantasmal.cell.mutableCell
import world.phantasmal.cell.orElse
import kotlin.time.Duration
class HuntMethodModel(

Some files were not shown because too many files have changed in this diff Show More