@@ -5,6 +5,9 @@ import io.ktor.client.engine.HttpClientEngine
5
5
import io.ktor.client.engine.HttpClientEngineFactory
6
6
import io.ktor.client.plugins.websocket.WebSockets
7
7
import kotlinx.coroutines.delay
8
+ import org.modelix.incremental.DependencyTracking
9
+ import org.modelix.incremental.IStateVariableGroup
10
+ import org.modelix.incremental.IStateVariableReference
8
11
import org.modelix.model.api.ConceptReference
9
12
import org.modelix.model.api.IBranch
10
13
import org.modelix.model.api.IConcept
@@ -60,9 +63,9 @@ class LightModelClient internal constructor(
60
63
val autoFilterNonLoadedNodes : Boolean ,
61
64
val debugName : String = " " ,
62
65
val modelQLClient : ModelQLClient ? = null ,
63
- ) {
66
+ ) : IStateVariableGroup {
64
67
65
- private val nodes: MutableMap < NodeId , NodeData > = HashMap ( )
68
+ private val nodes = NodesMap < NodeData >( this )
66
69
private val area = Area ()
67
70
private var areaListeners: Set <IAreaListener > = emptySet()
68
71
private var repositoryId: String? = null
@@ -73,8 +76,7 @@ class LightModelClient internal constructor(
73
76
private var temporaryIdsSequence: Long = 0
74
77
private var changeSetIdSequence: Int = 0
75
78
private val nodesReferencingTemporaryIds = HashSet <NodeId >()
76
- private var writeLevel: Int = 0
77
- private val temporaryNodeAdapters: MutableMap <String , NodeAdapter > = HashMap ()
79
+ private val temporaryNodeAdapters = NodesMap <NodeAdapter >(this )
78
80
private var initialized = false
79
81
private var lastUnconfirmedChangeSetId: ChangeSetId ? = null
80
82
private val unappliedVersions: MutableList <VersionData > = ArrayList ()
@@ -91,6 +93,7 @@ class LightModelClient internal constructor(
91
93
}
92
94
}
93
95
transactionManager.afterWrite {
96
+ flush()
94
97
val changes = object : IAreaChangeList {
95
98
override fun visitChanges (visitor : (IAreaChangeEvent ) -> Boolean ) {}
96
99
}
@@ -104,6 +107,10 @@ class LightModelClient internal constructor(
104
107
}
105
108
}
106
109
110
+ override fun getGroup (): IStateVariableGroup ? {
111
+ return null
112
+ }
113
+
107
114
fun dispose () {
108
115
connection.disconnect()
109
116
}
@@ -154,15 +161,7 @@ class LightModelClient internal constructor(
154
161
}
155
162
156
163
fun <T > runWrite (body : () -> T ): T {
157
- return transactionManager.runWrite {
158
- writeLevel++
159
- try {
160
- body()
161
- } finally {
162
- writeLevel--
163
- if (writeLevel == 0 ) flush()
164
- }
165
- }
164
+ return transactionManager.runWrite(body)
166
165
}
167
166
168
167
suspend fun waitForRootNode (timeout : Duration = 30.seconds): INode ? {
@@ -202,6 +201,10 @@ class LightModelClient internal constructor(
202
201
}
203
202
}
204
203
204
+ fun tryGetParentId (nodeId : NodeId ): NodeId ? {
205
+ return requiresRead { nodes[nodeId]?.parent }
206
+ }
207
+
205
208
fun isInitialized (): Boolean = runRead { initialized }
206
209
207
210
private fun fullConsistencyCheck () {
@@ -218,10 +221,6 @@ class LightModelClient internal constructor(
218
221
// }
219
222
}
220
223
221
- fun hasTemporaryIds (): Boolean = requiresRead {
222
- temporaryNodeAdapters.isNotEmpty() || nodesReferencingTemporaryIds.isNotEmpty()
223
- }
224
-
225
224
fun getNode (nodeId : NodeId ): NodeAdapter {
226
225
return requiresRead {
227
226
getNodeData(nodeId) // fail fast if it doesn't exist
@@ -718,6 +717,7 @@ internal interface ITransactionManager {
718
717
private class ReadWriteLockTransactionManager : ITransactionManager {
719
718
private var writeListener: (() -> Unit )? = null
720
719
private val lock = ReadWriteLock ()
720
+ private var writeLevel = 0
721
721
override fun <T > requiresRead (body : () -> T ): T {
722
722
if (! lock.canRead()) throw IllegalStateException (" Not in a read transaction" )
723
723
return body()
@@ -728,16 +728,18 @@ private class ReadWriteLockTransactionManager : ITransactionManager {
728
728
}
729
729
override fun <T > runRead (body : () -> T ): T = lock.runRead(body)
730
730
override fun <T > runWrite (body : () -> T ): T {
731
- if (canWrite()) {
732
- return body()
733
- } else {
731
+ return lock.runWrite {
732
+ writeLevel++
734
733
try {
735
- return lock.runWrite( body)
734
+ body( )
736
735
} finally {
737
- try {
738
- writeListener?.invoke()
739
- } catch (ex: Exception ) {
740
- mu.KotlinLogging .logger { }.error(ex) { " Exception in write listener" }
736
+ writeLevel--
737
+ if (writeLevel == 0 ) {
738
+ try {
739
+ writeListener?.invoke()
740
+ } catch (ex: Exception ) {
741
+ mu.KotlinLogging .logger { }.error(ex) { " Exception in write listener" }
742
+ }
741
743
}
742
744
}
743
745
}
@@ -901,3 +903,62 @@ fun NodeData.asUpdateData(): NodeUpdateData {
901
903
fun INode.isLoaded () = isValid
902
904
fun <T : INode > Iterable<T>.filterLoaded () = filter { it.isLoaded() }
903
905
fun <T : INode > Sequence<T>.filterLoaded () = filter { it.isLoaded() }
906
+
907
+ data class NodeDataDependency (val client : LightModelClient , val id : NodeId ) : IStateVariableReference<NodeData> {
908
+ override fun getGroup (): IStateVariableGroup {
909
+ return client.tryGetParentId(id)
910
+ ?.let { NodeDataDependency (client, it) }
911
+ ? : client
912
+ }
913
+
914
+ override fun read (): NodeData {
915
+ return client.getNode(id).getData()
916
+ }
917
+ }
918
+
919
+ private class NodesMap <V : Any >(val client : LightModelClient ) {
920
+ private val map: MutableMap <NodeId , V > = HashMap ()
921
+
922
+ operator fun get (key : NodeId ): V ? {
923
+ DependencyTracking .accessed(NodeDataDependency (client, key))
924
+ return map[key]
925
+ }
926
+
927
+ operator fun set (key : NodeId , value : V ) {
928
+ if (map[key] == value) return
929
+ map[key] = value
930
+ DependencyTracking .modified(NodeDataDependency (client, key))
931
+ }
932
+
933
+ fun remove (key : NodeId ): V ? {
934
+ if (! map.containsKey(key)) return null
935
+ val result = map.remove(key)
936
+ DependencyTracking .modified(NodeDataDependency (client, key))
937
+ return result
938
+ }
939
+
940
+ fun clear () {
941
+ if (map.isEmpty()) return
942
+ val removedKeys = map.keys.toList()
943
+ map.clear()
944
+ for (key in removedKeys) {
945
+ DependencyTracking .modified(NodeDataDependency (client, key))
946
+ }
947
+ }
948
+
949
+ fun containsKey (key : NodeId ): Boolean {
950
+ DependencyTracking .accessed(NodeDataDependency (client, key))
951
+ return map.containsKey(key)
952
+ }
953
+
954
+ fun getOrPut (key : NodeId , defaultValue : () -> V ): V {
955
+ DependencyTracking .accessed(NodeDataDependency (client, key))
956
+ map[key]?.let { return it }
957
+ val createdValue = defaultValue()
958
+ map[key] = createdValue
959
+ // No modified notification necessary, because only the first access modifies the map, but then there can't be
960
+ // any dependency on that key yet.
961
+ // DependencyTracking.modified(NodeDataDependency(client, key))
962
+ return createdValue
963
+ }
964
+ }
0 commit comments