@@ -15,6 +15,22 @@ REMOTE_ORIGIN <- NULL
1515 if (inherits(value , " Prelim" )) value else yr :: Prelim $ any(value )
1616}
1717
18+ # ' Holds the currently-active `yr::Transaction`, or NULL. Shared by a
19+ # ' WidgetBase and its storages so storage operations can join an ongoing
20+ # ' transaction instead of opening a nested one.
21+ # '
22+ # ' We need this for syntaxix sugar because the getter/setter on widget work
23+ # ' without function calls (as Python property) so there is no alternative place
24+ # ' to pass a transaction argument.
25+ # '
26+ # ' @noRd
27+ TransactionState <- R6 :: R6Class(
28+ " TransactionState" ,
29+ public = list (
30+ transaction = NULL
31+ )
32+ )
33+
1834# ' Shared base for `YAttrWidget` and `YRootWidget`
1935# '
2036# ' Owns the `yr::Doc` and the per-attribute storage registry, and provides
@@ -33,6 +49,7 @@ WidgetBase <- R6::R6Class(
3349 initialize = function (ydoc = NULL ) {
3450 self $ ydoc <- if (is.null(ydoc )) yr :: Doc $ new() else ydoc
3551 private $ .storages <- list ()
52+ private $ .active_transaction <- TransactionState $ new()
3653 },
3754
3855 # ' @description Subscribe callbacks to attribute changes by name. Each
@@ -56,11 +73,73 @@ WidgetBase <- R6::R6Class(
5673 # ' @param ... Subclass-specific arguments (e.g. `default` or `prelim`).
5774 register_storage = function (name , ... ) {
5875 stop(" register_storage() must be implemented by a subclass." )
76+ },
77+
78+ # ' @description Run `fn(trans)` inside a read-only transaction, exposing
79+ # ' `trans` as the active transaction so storage reads join it.
80+ # ' @param fn Function called with the transaction.
81+ with_read = function (fn ) {
82+ private $ with_active_transaction(
83+ fn ,
84+ mutable = FALSE ,
85+ origin = LOCAL_ORIGIN
86+ )
87+ },
88+
89+ # ' @description Run `fn(trans)` inside a writable transaction tagged with
90+ # ' `LOCAL_ORIGIN`, exposing `trans` as the active transaction so storage
91+ # ' writes join it.
92+ # ' @param fn Function called with the transaction.
93+ with_write = function (fn ) {
94+ private $ with_active_transaction(fn , mutable = TRUE , origin = LOCAL_ORIGIN )
5995 }
6096 ),
6197
6298 private = list (
63- .storages = NULL
99+ .storages = NULL ,
100+ .active_transaction = NULL ,
101+
102+ # Run `fn(trans)` inside a `with_transaction`, exposing `trans` as the
103+ # active transaction to storages for the duration of the call.
104+ with_active_transaction = function (fn , ... ) {
105+ state <- private $ .active_transaction
106+ self $ ydoc $ with_transaction(
107+ function (trans ) {
108+ state $ transaction <- trans
109+ on.exit(state $ transaction <- NULL )
110+ fn(trans )
111+ },
112+ ...
113+ )
114+ }
115+ )
116+ )
117+
118+ # ' Storage mixin that joins an ongoing transaction when one is active.
119+ # ' Holds a TransactionState shared with the owning widget and exposes a
120+ # ' private with_transaction() that runs fn(trans) on the active transaction
121+ # ' when set, and otherwise opens a fresh one on ydoc.
122+ # ' @noRd
123+ YActiveTransactionStorage <- R6 :: R6Class(
124+ " YActiveTransactionStorage" ,
125+ private = list (
126+ ydoc = NULL ,
127+ active_transaction = NULL ,
128+
129+ with_transaction = function (fn , mutable = FALSE , origin = NULL ) {
130+ active <- private $ active_transaction $ transaction
131+ if (! is.null(active )) {
132+ return (fn(active ))
133+ }
134+ private $ ydoc $ with_transaction(fn , mutable = mutable , origin = origin )
135+ }
136+ ),
137+
138+ public = list (
139+ initialize = function (ydoc , active_transaction ) {
140+ private $ ydoc <- ydoc
141+ private $ active_transaction <- active_transaction
142+ }
64143 )
65144)
66145
@@ -74,8 +153,8 @@ WidgetBase <- R6::R6Class(
74153# ' @export
75154YAttrStorage <- R6 :: R6Class(
76155 " YAttrStorage" ,
156+ inherit = YActiveTransactionStorage ,
77157 private = list (
78- ydoc = NULL ,
79158 attrs = " _attrs" ,
80159 key = NULL
81160 ),
@@ -89,10 +168,19 @@ YAttrStorage <- R6::R6Class(
89168 # ' @param ydoc The `yr::Doc`.
90169 # ' @param attrs Its attribute map.
91170 # ' @param key Attribute key to read/write.
171+ # ' @param active_transaction A [TransactionState] shared with the owning
172+ # ' widget; when its `transaction` is non-NULL, storage reads/writes join
173+ # ' that transaction instead of opening a new one.
92174 # ' @param default Default value (Prelim or any R value), or `NULL` to
93175 # ' skip the initial write entirely.
94- initialize = function (ydoc , attrs , key , default = NULL ) {
95- private $ ydoc <- ydoc
176+ initialize = function (
177+ ydoc ,
178+ attrs ,
179+ key ,
180+ active_transaction ,
181+ default = NULL
182+ ) {
183+ super $ initialize(ydoc , active_transaction )
96184 private $ attrs <- attrs
97185 private $ key <- key
98186 self $ remote_changed <- Signal $ new()
@@ -101,7 +189,7 @@ YAttrStorage <- R6::R6Class(
101189 # It may already be present if joining another widget.
102190 if (! is.null(default )) {
103191 prelim_default <- .as_prelim(default )
104- private $ ydoc $ with_transaction(
192+ private $ with_transaction(
105193 function (trans ) {
106194 if (is.null(private $ attrs $ get(trans , private $ key ))) {
107195 private $ attrs $ insert(trans , private $ key , prelim_default )
@@ -115,7 +203,7 @@ YAttrStorage <- R6::R6Class(
115203
116204 # ' @description Return the value stored under `key`.
117205 read = function () {
118- private $ ydoc $ with_transaction(
206+ private $ with_transaction(
119207 function (trans ) private $ attrs $ get(trans , private $ key )
120208 )
121209 },
@@ -124,7 +212,7 @@ YAttrStorage <- R6::R6Class(
124212 # ' @param value New value.
125213 # ' @return `TRUE` iff the value was written.
126214 update = function (value ) {
127- private $ ydoc $ with_transaction(
215+ private $ with_transaction(
128216 function (trans ) {
129217 if (identical(private $ attrs $ get(trans , private $ key ), value )) {
130218 return (FALSE )
@@ -186,7 +274,13 @@ YAttrWidget <- R6::R6Class(
186274 # ' @param default Default value (Prelim or any R value).
187275 # ' @return The newly created [YAttrStorage].
188276 register_storage = function (name , default ) {
189- storage <- YAttrStorage $ new(self $ ydoc , private $ .attrs , name , default )
277+ storage <- YAttrStorage $ new(
278+ self $ ydoc ,
279+ private $ .attrs ,
280+ name ,
281+ private $ .active_transaction ,
282+ default
283+ )
190284 private $ .storages [[name ]] <- storage
191285 storage
192286 }
@@ -208,6 +302,7 @@ YAttrWidget <- R6::R6Class(
208302# ' @export
209303YRootStorage <- R6 :: R6Class(
210304 " YRootStorage" ,
305+ inherit = YActiveTransactionStorage ,
211306 private = list (
212307 ref = NULL ,
213308
@@ -233,10 +328,14 @@ YRootStorage <- R6::R6Class(
233328 # ' @param name Root name on the doc.
234329 # ' @param prelim A `yr::Prelim` whose `is_text/is_map/is_array` selects
235330 # ' the root kind. Content is ignored.
236- initialize = function (ydoc , name , prelim ) {
331+ # ' @param active_transaction A [TransactionState] shared with the owning
332+ # ' widget. Stored for symmetry with [YAttrStorage]; root reads/writes
333+ # ' currently go through the ref directly and do not consult it.
334+ initialize = function (ydoc , name , prelim , active_transaction ) {
237335 if (! inherits(prelim , " Prelim" )) {
238336 stop(" YRootStorage requires a yr::Prelim for '" , name , " '." )
239337 }
338+ super $ initialize(ydoc , active_transaction )
240339 private $ ref <- private $ insert_root(ydoc , name , prelim )
241340 self $ remote_changed <- Signal $ new()
242341 sig <- self $ remote_changed
@@ -287,7 +386,12 @@ YRootWidget <- R6::R6Class(
287386 # ' @param prelim A `yr::Prelim` whose kind selects the root type.
288387 # ' @return The newly created [YRootStorage].
289388 register_storage = function (name , prelim ) {
290- storage <- YRootStorage $ new(self $ ydoc , name , prelim )
389+ storage <- YRootStorage $ new(
390+ self $ ydoc ,
391+ name ,
392+ prelim ,
393+ private $ .active_transaction
394+ )
291395 private $ .storages [[name ]] <- storage
292396 storage
293397 }
0 commit comments