File Coverage

blib/lib/Tangence/ObjectProxy.pm
Criterion Covered Total %
statement 375 415 90.3
branch 93 146 63.7
condition 30 64 46.8
subroutine 39 42 92.8
pod 11 15 73.3
total 548 682 80.3


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2010-2024 -- leonerd@leonerd.org.uk
5              
6 9     9   139 use v5.26;
  9         41  
7 9     9   61 use warnings;
  9         21  
  9         640  
8 9     9   57 use Object::Pad 0.800;
  9         97  
  9         517  
9              
10             package Tangence::ObjectProxy 0.33;
11             class Tangence::ObjectProxy;
12              
13 9     9   4203 use Carp;
  9         46  
  9         857  
14              
15 9     9   68 use Syntax::Keyword::Match 0.06;
  9         268  
  9         76  
16              
17 9     9   1740 use Future::AsyncAwait;
  9         27561  
  9         90  
18 9     9   719 use Future::Exception;
  9         21  
  9         395  
19              
20 9     9   53 use Tangence::Constants;
  9         19  
  9         2622  
21              
22 9     9   94 use Tangence::Types;
  9         20  
  9         824  
23              
24 9     9   66 use Scalar::Util qw( weaken );
  9         21  
  9         6977  
25              
26             =head1 NAME
27              
28             C - proxy for a C object in a
29             C
30              
31             =head1 DESCRIPTION
32              
33             Instances in this class act as a proxy for an object in the
34             L, allowing methods to be called, events to be subscribed
35             to, and properties to be watched.
36              
37             These objects are not directly constructed by calling the C class method;
38             instead they are returned by methods on L, or by methods on
39             other C instances. Ultimately every object proxy that a
40             client uses will come from either the proxy to the registry, or the root
41             object.
42              
43             =cut
44              
45 19     19 0 57 field $_client :param :weak :reader;
  19         76  
46 56     56 1 158 field $_id :param :reader;
  56         351  
47 0     0 1 0 field $_class :param :reader;
  0         0  
48              
49             field $_destroyed;
50              
51             field %_subscriptions;
52             field %_props;
53              
54             method destroy
55             {
56             $_destroyed = 1;
57              
58             foreach my $cb ( @{ $_subscriptions{destroy} } ) {
59             $cb->();
60             }
61             }
62              
63             =head1 METHODS
64              
65             The following methods documented in an C expression return L
66             instances.
67              
68             =cut
69              
70 9     9   86 use overload '""' => \&STRING;
  9         23  
  9         96  
71              
72             method STRING
73             {
74             return "Tangence::ObjectProxy[id=$_id]";
75             }
76              
77             =head2 id
78              
79             $id = $proxy->id;
80              
81             Returns the object ID for the C object being proxied for.
82              
83             =cut
84              
85             # generated accessor
86              
87             =head2 classname
88              
89             $classname = $proxy->classname;
90              
91             Returns the name of the class of the C object being proxied for.
92              
93             =cut
94              
95             method classname
96             {
97             return $_class->name;
98             }
99              
100             =head2 class
101              
102             $class = $proxyobj->class;
103              
104             Returns the L object representing the class of this
105             object.
106              
107             =cut
108              
109             # generated accessor
110              
111             =head2 can_method
112              
113             $method = $proxy->can_method( $name );
114              
115             Returns the L object representing the named method, or
116             C if no such method exists.
117              
118             =cut
119              
120             method can_method
121             {
122             return $_class->method( @_ );
123             }
124              
125             =head2 can_event
126              
127             $event = $proxy->can_event( $name );
128              
129             Returns the L object representing the named event, or
130             C if no such event exists.
131              
132             =cut
133              
134             method can_event
135             {
136             return $_class->event( @_ );
137             }
138              
139             =head2 can_property
140              
141             $property = $proxy->can_property( $name );
142              
143             Returns the L object representing the named
144             property, or C if no such property exists.
145              
146             =cut
147              
148             method can_property
149             {
150             return $_class->property( @_ );
151             }
152              
153             # Don't want to call it "isa"
154             method proxy_isa
155             {
156             if( @_ ) {
157             my ( $class ) = @_;
158             return !! grep { $_->name eq $class } $_class, $_class->superclasses;
159             }
160             else {
161             return $_class, $_class->superclasses
162             }
163             }
164              
165 10     10 0 21 method grab ( $smashdata )
  10         34  
  10         38  
  10         24  
166             {
167 10         20 foreach my $property ( keys %{ $smashdata } ) {
  10         50  
168 20         52 my $value = $smashdata->{$property};
169 20         115 my $dim = $self->can_property( $property )->dimension;
170              
171 20 50       66 if( $dim == DIM_OBJSET ) {
172             # Comes across in a LIST. We need to map id => obj
173 0         0 $value = { map { $_->id => $_ } @$value };
  0         0  
174             }
175              
176 20   50     113 my $prop = $_props{$property} ||= {};
177 20         93 $prop->{cache} = $value;
178             }
179             }
180              
181             =head2 call_method
182              
183             $result = await $proxy->call_method( $mname, @args );
184              
185             Calls the given method on the server object, passing in the given arguments.
186             Returns a L that will yield the method's result.
187              
188             =cut
189              
190 6     6 1 2033 async method call_method ( $method, @args )
  6         27  
  6         13  
  6         17  
  6         9  
191 6         14 {
192             # Detect void-context legacy uses
193             defined wantarray or
194 6 50       19 croak "->call_method in void context no longer useful - it now returns a Future";
195              
196 6 100       29 my $mdef = $self->can_method( $method )
197             or croak "Class ".$self->classname." does not have a method $method";
198              
199 4         39 my $request = Tangence::Message->new( $_client, MSG_CALL )
200             ->pack_int( $self->id )
201             ->pack_str( $method );
202              
203 4         29 my @argtypes = $mdef->argtypes;
204 4         28 $argtypes[$_]->pack_value( $request, $args[$_] ) for 0..$#argtypes;
205              
206 4         23 my $message = await $_client->request( request => $request );
207              
208 3         235 my $code = $message->code;
209              
210 3 50       12 if( $code == MSG_RESULT ) {
211 3 100       15 my $result = $mdef->ret ? $mdef->ret->unpack_value( $message )
212             : undef;
213 3         57 return $result;
214             }
215             else {
216 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
217             }
218             }
219              
220             =head2 subscribe_event
221              
222             await $proxy->subscribe_event( $event, %callbacks );
223              
224             Subscribes to the given event on the server object, installing a callback
225             function which will be invoked whenever the event is fired.
226              
227             Takes the following named callbacks:
228              
229             =over 8
230              
231             =item on_fire => CODE
232              
233             Callback function to invoke whenever the event is fired
234              
235             $on_fire->( @args );
236              
237             The returned C it is guaranteed to be completed before any invocation
238             of the C event handler.
239              
240             =back
241              
242             =cut
243              
244 7     7 1 13588 async method subscribe_event ( $event, %args )
  7         38  
  7         16  
  7         23  
  7         11  
245 7         21 {
246             # Detect void-context legacy uses
247             defined wantarray or
248 7 50       75 croak "->subscribe_event in void context no longer useful - it now returns a Future";
249              
250 7 50       40 ref( my $callback = delete $args{on_fire} ) eq "CODE"
251             or croak "Expected 'on_fire' as a CODE ref";
252              
253 7 100       39 $self->can_event( $event )
254             or croak "Class ".$self->classname." does not have an event $event";
255              
256 5 50       23 if( my $cbs = $_subscriptions{$event} ) {
257 0         0 push @$cbs, $callback;
258 0         0 return;
259             }
260              
261 5         16 my @cbs = ( $callback );
262 5         17 $_subscriptions{$event} = \@cbs;
263              
264 5 100       40 return if $event eq "destroy"; # This is automatically handled
265              
266 3         34 my $message = await $_client->request(
267             request => Tangence::Message->new( $_client, MSG_SUBSCRIBE )
268             ->pack_int( $self->id )
269             ->pack_str( $event )
270             );
271              
272 2         121 my $code = $message->code;
273              
274 2 50       11 if( $code == MSG_SUBSCRIBED ) {
275 2         19 return;
276             }
277             else {
278 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
279             }
280             }
281              
282 2     2 0 5 method handle_request_EVENT ( $message )
  2         6  
  2         7  
  2         3  
283             {
284 2         35 my $event = $message->unpack_str();
285 2 50       28 my $edef = $self->can_event( $event ) or return;
286              
287 2         14 my @args = map { $_->unpack_value( $message ) } $edef->argtypes;
  4         17  
288              
289 2 50       29 if( my $cbs = $_subscriptions{$event} ) {
290 2         6 foreach my $cb ( @$cbs ) { $cb->( @args ) }
  2         9  
291             }
292             }
293              
294             =head2 unsubscribe_event
295              
296             $proxy->unsubscribe_event( $event );
297              
298             Removes an event subscription on the given event on the server object that was
299             previously installed using C.
300              
301             =cut
302              
303 2     2 1 1093 method unsubscribe_event ( $event )
  2         12  
  2         6  
  2         4  
304             {
305 2 50       11 $self->can_event( $event )
306             or croak "Class ".$self->classname." does not have an event $event";
307              
308 2 50       12 return if $event eq "destroy"; # This is automatically handled
309              
310             $_client->request(
311             request => Tangence::Message->new( $_client, MSG_UNSUBSCRIBE )
312             ->pack_int( $self->id )
313             ->pack_str( $event ),
314              
315       2     on_response => sub {},
316 2         18 );
317             }
318              
319             =head2 get_property
320              
321             await $value = $proxy->get_property( $prop );
322              
323             Requests the current value of the property from the server object.
324              
325             =cut
326              
327 8     8 1 810 async method get_property ( $property )
  8         33  
  8         19  
  8         15  
328 8         22 {
329             # Detect void-context legacy uses
330             defined wantarray or
331 8 50       68 croak "->get_property in void context no longer useful - it now returns a Future";
332              
333 8 100       36 my $pdef = $self->can_property( $property )
334             or croak "Class ".$self->classname." does not have a property $property";
335              
336 6         59 my $message = await $_client->request(
337             request => Tangence::Message->new( $_client, MSG_GETPROP )
338             ->pack_int( $self->id )
339             ->pack_str( $property ),
340             );
341              
342 5         337 my $code = $message->code;
343              
344 5 50       23 if( $code == MSG_RESULT ) {
345 5         32 return $pdef->overall_type->unpack_value( $message );
346             }
347             else {
348 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
349             }
350             }
351              
352             =head2 get_property_element
353              
354             await $value = $proxy->get_property_element( $property, $index_or_key );
355              
356             Requests the current value of an element of the property from the server
357             object.
358              
359             =cut
360              
361 4     4 1 4843 async method get_property_element ( $property, $index_or_key )
  4         17  
  4         11  
  4         7  
  4         8  
362 4         13 {
363             # Detect void-context legacy uses
364             defined wantarray or
365 4 50       18 croak "->get_property_element in void context no longer useful - it now returns a Future";
366              
367 4 50       19 my $pdef = $self->can_property( $property )
368             or croak "Class ".$self->classname." does not have a property $property";
369              
370 4         35 my $request = Tangence::Message->new( $_client, MSG_GETPROPELEM )
371             ->pack_int( $self->id )
372             ->pack_str( $property );
373              
374             match( $pdef->dimension : == ) {
375             case( DIM_HASH ) {
376 2         7 $request->pack_str( $index_or_key );
377             }
378             case( DIM_ARRAY ), case( DIM_QUEUE ) {
379 2         43 $request->pack_int( $index_or_key );
380             }
381 4 100 33     19 default {
    50          
382 0         0 croak "Cannot get_property_element of a non hash, array or queue";
383             }
384             }
385              
386 4         19 my $message = await $_client->request(
387             request => $request,
388             );
389              
390 4         226 my $code = $message->code;
391              
392 4 50       13 if( $code == MSG_RESULT ) {
393 4         19 return $pdef->type->unpack_value( $message );
394             }
395             else {
396 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
397             }
398             }
399              
400             =head2 prop
401              
402             $value = $proxy->prop( $property );
403              
404             Returns the locally-cached value of a smashed property. If the named property
405             is not a smashed property, an exception is thrown.
406              
407             =cut
408              
409 17     17 1 975 method prop ( $property )
  17         58  
  17         37  
  17         23  
410             {
411 17 50       69 if( exists $_props{$property}->{cache} ) {
412 17         149 return $_props{$property}->{cache};
413             }
414              
415 0         0 croak "$self does not have a cached property '$property'";
416             }
417              
418             =head2 set_property
419              
420             await $proxy->set_property( $prop, $value );
421              
422             Sets the value of the property in the server object.
423              
424             =cut
425              
426 9     9 1 15489 async method set_property ( $property, $value )
  9         41  
  9         22  
  9         16  
  9         19  
427 9         23 {
428             # Detect void-context legacy uses
429             defined wantarray or
430 9 50       33 croak "->set_property in void context no longer useful - it now returns a Future";
431              
432 9 50       43 my $pdef = $self->can_property( $property )
433             or croak "Class ".$self->classname." does not have a property $property";
434              
435 9         84 my $request = Tangence::Message->new( $_client, MSG_SETPROP )
436             ->pack_int( $self->id )
437             ->pack_str( $property );
438 9         62 $pdef->overall_type->pack_value( $request, $value );
439              
440 9         44 my $message = await $_client->request(
441             request => $request,
442             );
443              
444 7         257 my $code = $message->code;
445              
446 7 50       26 if( $code == MSG_OK ) {
447 7         95 return;
448             }
449             else {
450 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
451             }
452             }
453              
454             =head2 watch_property
455              
456             await $proxy->watch_property( $property, %callbacks );
457              
458             =head2 watch_property_with_initial
459              
460             await $proxy->watch_property_with_initial( $property, %callbacks );
461              
462             Watches the given property on the server object, installing callback functions
463             which will be invoked whenever the property value changes. The latter form
464             additionally ensures that the server will send the current value of the
465             property as an initial update to the C event, atomically when it
466             installs the update watches.
467              
468             Takes the following named arguments:
469              
470             =over 8
471              
472             =item on_updated => CODE
473              
474             Optional. Callback function to invoke whenever the property value changes.
475              
476             $on_updated->( $new_value );
477              
478             If not provided, then individual handlers for individual change types must be
479             provided.
480              
481             =back
482              
483             The set of callback functions that are required depends on the type of the
484             property. These are documented in the C method of
485             L.
486              
487             =cut
488              
489 24         52 sub _watchcbs_from_args ( $pdef, %args )
490 24     24   69 {
  24         65  
  24         67  
491 24         48 my $callbacks = {};
492 24         72 my $on_updated = delete $args{on_updated};
493 24 100       110 if( $on_updated ) {
494 5 50       28 ref $on_updated eq "CODE" or croak "Expected 'on_updated' to be a CODE ref";
495 5         17 $callbacks->{on_updated} = $on_updated;
496             }
497              
498 24         50 foreach my $name ( @{ CHANGETYPES->{$pdef->dimension} } ) {
  24         138  
499             # All of these become optional if 'on_updated' is supplied
500 52 100 66     202 next if $on_updated and not exists $args{$name};
501              
502 37 50       194 ref( $callbacks->{$name} = delete $args{$name} ) eq "CODE"
503             or croak "Expected '$name' as a CODE ref";
504             }
505              
506 24         69 return $callbacks;
507             }
508              
509             method watch_property { $self->_watch_property( shift, 0, @_ ) }
510             method watch_property_with_initial { $self->_watch_property( shift, 1, @_ ) }
511              
512 18     18   46 async method _watch_property ( $property, $want_initial, %args )
  18         67  
  18         42  
  18         37  
  18         70  
  18         32  
513 18         54 {
514             # Detect void-context legacy uses
515             defined wantarray or
516 18 50       64 croak "->watch_property in void context no longer useful - it now returns a Future";
517              
518 18 50       94 my $pdef = $self->can_property( $property )
519             or croak "Class ".$self->classname." does not have a property $property";
520              
521 18         96 my $callbacks = _watchcbs_from_args( $pdef, %args );
522              
523             # Smashed properties behave differently
524 18         92 my $smash = $pdef->smashed;
525              
526 18 100       96 if( my $cbs = $_props{$property}->{cbs} ) {
527 3 50 33     22 if( $want_initial and !$smash ) {
    0 0        
528 3         20 my $value = await $self->get_property( $property );
529              
530 3 100       253 $callbacks->{on_set} and $callbacks->{on_set}->( $value );
531 3 100       26 $callbacks->{on_updated} and $callbacks->{on_updated}->( $value );
532 3         12 push @$cbs, $callbacks;
533 3         30 return;
534             }
535             elsif( $want_initial and $smash ) {
536 0         0 my $cache = $_props{$property}->{cache};
537 0 0       0 $callbacks->{on_set} and $callbacks->{on_set}->( $cache );
538 0 0       0 $callbacks->{on_updated} and $callbacks->{on_updated}->( $cache );
539 0         0 push @$cbs, $callbacks;
540 0         0 return;
541             }
542             else {
543 0         0 push @$cbs, $callbacks;
544 0         0 return;
545             }
546              
547 0         0 die "UNREACHED";
548             }
549              
550 15         81 $_props{$property}->{cbs} = [ $callbacks ];
551              
552 15 100       107 if( $smash ) {
553 2 50       9 if( $want_initial ) {
554 2         6 my $cache = $_props{$property}->{cache};
555 2 50       14 $callbacks->{on_set} and $callbacks->{on_set}->( $cache );
556 2 50       12 $callbacks->{on_updated} and $callbacks->{on_updated}->( $cache );
557             }
558              
559 2         37 return;
560             }
561              
562 13         121 my $request = Tangence::Message->new( $_client, MSG_WATCH )
563             ->pack_int( $self->id )
564             ->pack_str( $property )
565             ->pack_bool( $want_initial );
566              
567 13         79 my $message = await $_client->request( request => $request );
568              
569 12         492 my $code = $message->code;
570              
571 12 50       69 if( $code == MSG_WATCHING ) {
572 12         244 return;
573             }
574             else {
575 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
576             }
577             }
578              
579             =head2 watch_property_with_cursor
580              
581             ( $cursor, $first_idx, $last_idx ) =
582             await $proxy->watch_property_with_cursor( $property, $from, %callbacks );
583              
584             A variant of C that installs a watch on the given property of
585             the server object, and additionally returns an cursor object that can be used
586             to lazily fetch the values stored in it.
587              
588             The C<$from> value indicates which end of the queue the cursor should start
589             from; C to start at index 0, or C to start at the
590             highest-numbered index. The cursor is created atomically with installing the
591             watch.
592              
593             =cut
594              
595             method watch_property_with_iter
596             {
597             # Detect void-context legacy uses
598             defined wantarray or
599             croak "->watch_property_with_iter in void context no longer useful - it now returns a Future";
600              
601             return $self->watch_property_with_cursor( @_ );
602             }
603              
604 6     6 1 1094 async method watch_property_with_cursor ( $property, $from, %args )
  6         40  
  6         16  
  6         15  
  6         23  
  6         13  
605 6         17 {
606             match( $from : eq ) {
607 5         14 case( "first" ) { $from = CUSR_FIRST }
608 1         3 case( "last" ) { $from = CUSR_LAST }
609 6 100       32 default { croak "Unrecognised 'from' value $from" }
  0 50       0  
610             }
611              
612 6 50       30 my $pdef = $self->can_property( $property )
613             or croak "Class ".$self->classname." does not have a property $property";
614              
615 6         31 my $callbacks = _watchcbs_from_args( $pdef, %args );
616              
617             # Smashed properties behave differently
618 6         28 my $smashed = $pdef->smashed;
619              
620 6 50       48 if( my $cbs = $_props{$property}->{cbs} ) {
621 0         0 die "TODO: need to synthesize a second cursor for $self";
622             }
623              
624 6         25 $_props{$property}->{cbs} = [ $callbacks ];
625              
626 6 50       31 if( $smashed ) {
627 0         0 die "TODO: need to synthesize an cursor";
628             }
629              
630 6 50       24 $pdef->dimension == DIM_QUEUE or croak "Can only iterate on queue-dimension properties";
631              
632 6         51 my $message = await $_client->request(
633             request => Tangence::Message->new( $_client, MSG_WATCH_CUSR )
634             ->pack_int( $self->id )
635             ->pack_str( $property )
636             ->pack_int( $from ),
637             );
638              
639 6         241 my $code = $message->code;
640              
641 6 50       38 if( $code == MSG_WATCHING_CUSR ) {
642 6         47 my $cursor_id = $message->unpack_int();
643 6         25 my $first_idx = $message->unpack_int();
644 6         24 my $last_idx = $message->unpack_int();
645              
646 6         32 my $cursor = Tangence::ObjectProxy::_Cursor->new( $self, $cursor_id, $pdef->type );
647 6         95 return ( $cursor, $first_idx, $last_idx );
648             }
649             else {
650 0         0 Future::Exception->throw( "Unexpected response code $code", tangence => );
651             }
652             }
653              
654 47     47 0 102 method handle_request_UPDATE ( $message )
  47         121  
  47         81  
  47         87  
655             {
656 47         199 my $prop = $message->unpack_str();
657 47         625 my $how = TYPE_U8->unpack_value( $message );
658              
659 47 50       240 my $pdef = $self->can_property( $prop ) or return;
660 47         256 my $type = $pdef->type;
661 47         169 my $dim = $pdef->dimension;
662              
663 47   50     190 my $p = $_props{$prop} ||= {};
664              
665 47         163 my $dimname = DIMNAMES->[$dim];
666 47 50       394 if( my $code = $self->can( "_update_property_$dimname" ) ) {
667 47         177 $code->( $self, $p, $type, $how, $message );
668             }
669             else {
670 0         0 croak "Unrecognised property dimension $dim for $prop";
671             }
672              
673 47   66     310 $_->{on_updated} and $_->{on_updated}->( $p->{cache} ) for @{ $p->{cbs} };
  47         524  
674             }
675              
676 15     15   50 method _update_property_scalar ( $p, $type, $how, $message )
  15         53  
  15         26  
  15         26  
  15         400  
  15         34  
  15         24  
677             {
678             match( $how : == ) {
679             case( CHANGE_SET ) {
680 15         63 my $value = $type->unpack_value( $message );
681 15         40 $p->{cache} = $value;
682 15   33     39 $_->{on_set} and $_->{on_set}->( $p->{cache} ) for @{ $p->{cbs} };
  15         114  
683             }
684 15 50       47 default {
685 0         0 croak "Change type $how is not valid for a scalar property";
686             }
687             }
688             }
689              
690 8     8   17 method _update_property_hash ( $p, $type, $how, $message )
  8         21  
  8         40  
  8         18  
  8         15  
  8         18  
  8         13  
691             {
692             match( $how : == ) {
693             case( CHANGE_SET ) {
694 2         16 my $value = Tangence::Type->make( dict => $type )->unpack_value( $message );
695 2         8 $p->{cache} = $value;
696 2   66     6 $_->{on_set} and $_->{on_set}->( $p->{cache} ) for @{ $p->{cbs} };
  2         35  
697             }
698             case( CHANGE_ADD ) {
699 4         19 my $key = $message->unpack_str();
700 4         71 my $value = $type->unpack_value( $message );
701 4         17 $p->{cache}->{$key} = $value;
702 4   66     9 $_->{on_add} and $_->{on_add}->( $key, $value ) for @{ $p->{cbs} };
  4         30  
703             }
704             case( CHANGE_DEL ) {
705 2         15 my $key = $message->unpack_str();
706 2         27 delete $p->{cache}->{$key};
707 2   66     7 $_->{on_del} and $_->{on_del}->( $key ) for @{ $p->{cbs} };
  2         21  
708             }
709 8 100       44 default {
    100          
    50          
710 0         0 croak "Change type $how is not valid for a hash property";
711             }
712             }
713             }
714              
715 7     7   15 method _update_property_queue ( $p, $type, $how, $message )
  7         20  
  7         16  
  7         14  
  7         13  
  7         15  
  7         10  
716             {
717             match( $how : == ) {
718             case( CHANGE_SET ) {
719 1         9 my $value = Tangence::Type->make( list => $type )->unpack_value( $message );
720 1         6 $p->{cache} = $value;
721 1   33     22 $_->{on_set} and $_->{on_set}->( $p->{cache} ) for @{ $p->{cbs} };
  1         16  
722             }
723             case( CHANGE_PUSH ) {
724 3         18 my @value = $message->unpack_all_sametype( $type );
725 3         7 push @{ $p->{cache} }, @value;
  3         13  
726 3   33     6 $_->{on_push} and $_->{on_push}->( @value ) for @{ $p->{cbs} };
  3         28  
727             }
728             case( CHANGE_SHIFT ) {
729 3         19 my $count = $message->unpack_int();
730 3         8 splice @{ $p->{cache} }, 0, $count, ();
  3         14  
731 3   33     9 $_->{on_shift} and $_->{on_shift}->( $count ) for @{ $p->{cbs} };
  3         30  
732             }
733 7 100       42 default {
    100          
    50          
734 0         0 croak "Change type $how is not valid for a queue property";
735             }
736             }
737             }
738              
739 13     13   26 method _update_property_array ( $p, $type, $how, $message )
  13         37  
  13         22  
  13         39  
  13         27  
  13         21  
  13         23  
740             {
741             match( $how : == ) {
742             case( CHANGE_SET ) {
743 4         29 my $value = Tangence::Type->make( list => $type )->unpack_value( $message );
744 4         15 $p->{cache} = $value;
745 4   66     13 $_->{on_set} and $_->{on_set}->( $p->{cache} ) for @{ $p->{cbs} };
  4         41  
746             }
747             case( CHANGE_PUSH ) {
748 3         19 my @value = $message->unpack_all_sametype( $type );
749 3         8 push @{ $p->{cache} }, @value;
  3         10  
750 3   66     9 $_->{on_push} and $_->{on_push}->( @value ) for @{ $p->{cbs} };
  3         27  
751             }
752             case( CHANGE_SHIFT ) {
753 1         8 my $count = $message->unpack_int();
754 1         5 splice @{ $p->{cache} }, 0, $count, ();
  1         6  
755 1   33     4 $_->{on_shift} and $_->{on_shift}->( $count ) for @{ $p->{cbs} };
  1         15  
756             }
757             case( CHANGE_SPLICE ) {
758 2         10 my $start = $message->unpack_int();
759 2         11 my $count = $message->unpack_int();
760 2         11 my @value = $message->unpack_all_sametype( $type );
761 2         5 splice @{ $p->{cache} }, $start, $count, @value;
  2         9  
762 2   66     5 $_->{on_splice} and $_->{on_splice}->( $start, $count, @value ) for @{ $p->{cbs} };
  2         19  
763             }
764             case( CHANGE_MOVE ) {
765 3         17 my $index = $message->unpack_int();
766 3         12 my $delta = $message->unpack_int();
767             # it turns out that exchanging neighbours is quicker by list assignment,
768             # but other times it's generally best to use splice() to extract then
769             # insert
770 3 50       15 if( abs($delta) == 1 ) {
771 0         0 @{$p->{cache}}[$index,$index+$delta] = @{$p->{cache}}[$index+$delta,$index];
  0         0  
  0         0  
772             }
773             else {
774 3         6 my $elem = splice @{ $p->{cache} }, $index, 1, ();
  3         12  
775 3         6 splice @{ $p->{cache} }, $index + $delta, 0, ( $elem );
  3         12  
776             }
777 3   66     7 $_->{on_move} and $_->{on_move}->( $index, $delta ) for @{ $p->{cbs} };
  3         26  
778             }
779 13 100       77 default {
    100          
    100          
    100          
    50          
780 0         0 croak "Change type $how is not valid for an array property";
781             }
782             }
783             }
784              
785 4     4   11 method _update_property_objset ( $p, $type, $how, $message )
  4         13  
  4         10  
  4         8  
  4         10  
  4         8  
  4         8  
786             {
787             match( $how : == ) {
788             case( CHANGE_SET ) {
789             # Comes across in a LIST. We need to map id => obj
790 2         16 my $objects = Tangence::Type->make( list => $type )->unpack_value( $message );
791 2         12 $p->{cache} = { map { $_->id => $_ } @$objects };
  1         9  
792 2   33     8 $_->{on_set} and $_->{on_set}->( $p->{cache} ) for @{ $p->{cbs} };
  2         53  
793             }
794             case( CHANGE_ADD ) {
795             # Comes as object only
796 1         12 my $obj = $type->unpack_value( $message );
797 1         9 $p->{cache}->{$obj->id} = $obj;
798 1   33     5 $_->{on_add} and $_->{on_add}->( $obj ) for @{ $p->{cbs} };
  1         14  
799             }
800             case( CHANGE_DEL ) {
801             # Comes as ID number only
802 1         7 my $id = $message->unpack_int();
803 1         6 delete $p->{cache}->{$id};
804 1   33     3 $_->{on_del} and $_->{on_del}->( $id ) for @{ $p->{cbs} };
  1         14  
805             }
806 4 100       25 default {
    100          
    50          
807 0         0 croak "Change type $how is not valid for an objset property";
808             }
809             }
810             }
811              
812             =head2 unwatch_property
813              
814             $proxy->unwatch_property( $property );
815              
816             Removes a property watches on the given property on the server object that was
817             previously installed using C.
818              
819             =cut
820              
821 4     4 1 5763 method unwatch_property ( $property )
  4         22  
  4         10  
  4         7  
822             {
823 4 50       20 $self->can_property( $property )
824             or croak "Class ".$self->classname." does not have a property $property";
825              
826             # TODO: mark cursors as destroyed and invalid
827 4         48 delete $_props{$property};
828              
829             $_client->request(
830             request => Tangence::Message->new( $_client, MSG_UNWATCH )
831             ->pack_int( $self->id )
832             ->pack_str( $property ),
833              
834       4     on_response => sub {},
835 4         38 );
836             }
837              
838             class Tangence::ObjectProxy::_Cursor
839             {
840 9     9   171330 use Carp;
  9         23  
  9         906  
841 9     9   64 use Tangence::Constants;
  9         22  
  9         20632  
842              
843             =head1 CURSOR METHODS
844              
845             The following methods are availilable on the property cursor objects returned
846             by the C method.
847              
848             =cut
849              
850 0     0   0 field $obj :param :reader;
  0         0  
851 0     0   0 field $id :param :reader;
  0         0  
852             field $element_type :param;
853              
854 6         16 sub BUILDARGS ( $class, $obj, $id, $element_type )
  6         13  
  6         19  
855 6     6   16 {
  6         14  
  6         12  
856 6         79 return ( obj => $obj, id => $id, element_type => $element_type );
857             }
858              
859             method client { $obj->client }
860              
861             # TODO: Object::Pad probably should do this bit
862             method DESTROY
863             {
864             return unless $obj and my $client = $self->client;
865              
866             $client->request(
867             request => Tangence::Message->new( $client, MSG_CUSR_DESTROY )
868             ->pack_int( $id ),
869              
870       6     on_response => sub {},
871             );
872             }
873              
874             =head2 next_forward
875              
876             ( $index, @more ) = await $cursor->next_forward( $count );
877              
878             =head2 next_backward
879              
880             ( $index, @more ) = await $cursor->next_backward( $count );
881              
882             Requests the next items from the cursor. C moves forwards
883             towards higher-numbered indices, and C moves backwards towards
884             lower-numbered indices. If C<$count> is unspecified, a default of 1 will
885             apply.
886              
887             The returned future wil yield the index of the first element returned, and the
888             new elements. Note that there may be fewer elements returned than were
889             requested, if the end of the queue was reached. Specifically, there will be no
890             new elements if the cursor is already at the end.
891              
892             =cut
893              
894             method next_forward
895             {
896             $self->_next( CUSR_FWD, @_ );
897             }
898              
899             method next_backward
900             {
901             $self->_next( CUSR_BACK, @_ );
902             }
903              
904 12     12   27 async method _next ( $direction, $count = 1 )
  12         55  
  12         1662  
  12         28  
  12         21  
905 12         25 {
906             # Detect void-context legacy uses
907             defined wantarray or
908 12 50       39 croak "->next_forward/backward in void context no longer useful - it now returns a Future";
909              
910 12         53 my $client = $self->client;
911              
912 12         99 my $message = await $client->request(
913             request => Tangence::Message->new( $client, MSG_CUSR_NEXT )
914             ->pack_int( $id )
915             ->pack_int( $direction )
916             ->pack_int( $count || 1 ),
917             );
918              
919 12         566 my $code = $message->code;
920              
921 12 50       36 if( $code == MSG_CUSR_RESULT ) {
922             return (
923 12         63 $message->unpack_int(),
924             $message->unpack_all_sametype( $element_type ),
925             );
926             }
927             else {
928 0           Future::Exception->throw( "Unexpected response code $code", tangence => );
929             }
930             }
931             }
932              
933             =head1 AUTHOR
934              
935             Paul Evans
936              
937             =cut
938              
939             0x55AA;