File Coverage

blib/lib/Tangence/Object.pm
Criterion Covered Total %
statement 289 304 95.0
branch 59 92 64.1
condition 7 11 63.6
subroutine 37 38 97.3
pod 13 22 59.0
total 405 467 86.7


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-2021 -- leonerd@leonerd.org.uk
5              
6             package Tangence::Object 0.30;
7              
8 14     14   169 use v5.26;
  14         51  
9 14     14   85 use warnings;
  14         29  
  14         405  
10 14     14   72 use experimental 'signatures';
  14         26  
  14         71  
11              
12 14     14   2219 use Carp;
  14         40  
  14         957  
13              
14 14     14   4804 use Syntax::Keyword::Match;
  14         8250  
  14         92  
15              
16 14     14   1621 use Tangence::Constants;
  14         34  
  14         2559  
17              
18 14     14   3835 use Tangence::Types;
  14         39  
  14         980  
19              
20 14     14   4092 use Tangence::Class;
  14         38  
  14         57885  
21              
22             Tangence::Class->declare(
23             __PACKAGE__,
24              
25             events => {
26             destroy => {
27             args => [],
28             },
29             },
30             );
31              
32             =head1 NAME
33              
34             C - base class for accessible objects in a C server
35              
36             =head1 DESCRIPTION
37              
38             This class acts as a base class for the accessible objects in a L
39             server. All the objects actually created and made accessible to clients will
40             be subclasses of this one, including internally-created objects such as
41             L.
42              
43             These objects are not directly constructed by calling the C class method;
44             instead the C should be used to construct one.
45              
46             =cut
47              
48 25         56 sub new ( $class, %args )
49 25     25 0 214 {
  25         88  
  25         41  
50 25 50       104 defined( my $id = delete $args{id} ) or croak "Need a id";
51 25 50       96 my $registry = delete $args{registry} or croak "Need a registry";
52              
53             my $self = bless {
54             id => $id,
55             registry => $registry,
56 25   66     182 meta => $args{meta} || Tangence::Class->for_perlname( $class ),
57              
58             event_subs => {}, # {$event} => [ @cbs ]
59              
60             properties => {}, # {$prop} => T:P::Instance struct
61             }, $class;
62              
63 25         159 my $properties = $self->class->properties;
64 25         82 foreach my $prop ( keys %$properties ) {
65 117         707 my $meth = "new_prop_$prop";
66 117         380 $self->$meth();
67             }
68              
69 25         396 return $self;
70             }
71              
72             =head1 METHODS
73              
74             =cut
75              
76             =head2 destroy
77              
78             $obj->destroy
79              
80             Requests that the object destroy itself, informing all clients that are aware
81             of it. Once they all report that they have dropped the object, the object is
82             deconstructed for real.
83              
84             Not to be confused with Perl's own C method.
85              
86             =cut
87              
88 2         5 sub destroy ( $self, %args )
89 2     2 1 486 {
  2         6  
  2         3  
90 2         7 $self->{destroying} = 1;
91              
92 2         6 my $outstanding = 1;
93              
94 2         7 my $on_destroyed = $args{on_destroyed};
95              
96             my $incsub = sub {
97 2     2   16 $outstanding++
98 2         9 };
99              
100             my $decsub = sub {
101 4 100   4   18 --$outstanding and return;
102 2         24 $self->_destroy_really;
103 2 50       12 $on_destroyed->() if $on_destroyed;
104 2         11 };
105              
106 2         4 foreach my $cb ( @{ $self->{event_subs}->{destroy} } ) {
  2         8  
107 2         9 $cb->( $self, $incsub, $decsub );
108             }
109              
110 2         7 $decsub->();
111             }
112              
113             sub _destroy_really
114             {
115 2     2   7 my $self = shift;
116              
117 2         12 $self->registry->destroy_object( $self );
118              
119 2         28 undef %$self; # Now I am dead
120 2         7 $self->{destroyed} = 1;
121             }
122              
123             =head2 id
124              
125             $id = $obj->id
126              
127             Returns the object's C ID number
128              
129             =cut
130              
131             sub id
132             {
133 106     106 1 3686 my $self = shift;
134 106         312 return $self->{id};
135             }
136              
137             =head2 describe
138              
139             $description = $obj->describe
140              
141             Returns a textual description of the object, for internal debugging purposes.
142             Subclasses are encouraged to override this method to return something more
143             descriptive within their domain of interest
144              
145             =cut
146              
147             sub describe
148             {
149 12     12 1 38 my $self = shift;
150 12         71 return ref $self;
151             }
152              
153             =head2 registry
154              
155             $registry = $obj->registry
156              
157             Returns the L that constructed this object.
158              
159             =cut
160              
161             sub registry
162             {
163 3     3 1 10 my $self = shift;
164 3         16 return $self->{registry};
165             }
166              
167 11         23 sub smash ( $self, $smashkeys )
168 11     11 0 23 {
  11         21  
  11         17  
169 11 50 33     84 return undef unless $smashkeys and @$smashkeys;
170              
171 11         28 my @keys;
172 11 50       85 if( ref $smashkeys eq "HASH" ) {
173 0         0 @keys = keys %$smashkeys;
174             }
175             else {
176 11         43 @keys = @$smashkeys;
177             }
178              
179             return { map {
180 11         35 my $m = "get_prop_$_";
  21         124  
181 21         100 $_ => $self->$m()
182             } @keys };
183             }
184              
185             =head2 class
186              
187             $class = $obj->class
188              
189             Returns the L object representing the class of this
190             object.
191              
192             =cut
193              
194             sub class
195             {
196 273     273 1 398 my $self = shift;
197 273 50       1286 return ref $self ? $self->{meta} : Tangence::Class->for_perlname( $self );
198             }
199              
200             =head2 can_method
201              
202             $method = $obj->can_method( $name )
203              
204             Returns the L object representing the named method, or
205             C if no such method exists.
206              
207             =cut
208              
209             sub can_method
210             {
211 5     5 1 535 my $self = shift;
212 5         15 return $self->class->method( @_ );
213             }
214              
215             =head2 can_event
216              
217             $event = $obj->can_event( $name )
218              
219             Returns the L object representing the named event, or
220             C if no such event exists.
221              
222             =cut
223              
224             sub can_event
225             {
226 48     48 1 610 my $self = shift;
227 48         119 return $self->class->event( @_ );
228             }
229              
230             =head2 can_property
231              
232             $property = $obj->can_property( $name )
233              
234             Returns the L object representing the named
235             property, or C if no such property exists.
236              
237             =cut
238              
239             sub can_property
240             {
241 170     170 1 778 my $self = shift;
242 170         402 return $self->class->property( @_ );
243             }
244              
245             sub smashkeys
246             {
247 1     1 0 572 my $self = shift;
248 1         3 return $self->class->smashkeys;
249             }
250              
251             =head2 fire_event
252              
253             $obj->fire_event( $event, @args )
254              
255             Fires the named event on the object. Each event subscription function will be
256             invoked with the given arguments.
257              
258             =cut
259              
260 18         39 sub fire_event ( $self, $event, @args )
  18         45  
261 18     18 1 389 {
  18         38  
  18         33  
262 18 50       64 $event eq "destroy" and croak "$self cannot fire destroy event directly";
263              
264 18 50       79 $self->can_event( $event ) or croak "$self has no event $event";
265              
266 18 100       111 my $sublist = $self->{event_subs}->{$event} or return;
267              
268 3         10 foreach my $cb ( @$sublist ) {
269 3         12 $cb->( $self, @args );
270             }
271             }
272              
273             =head2 subscribe_event
274              
275             $id = $obj->subscribe_event( $event, $callback )
276              
277             Subscribes an event-handling callback CODE ref to the named event. When the
278             event is fired by C this callback will be invoked, being passed
279             the object reference and the event's arguments.
280              
281             $callback->( $obj, @args )
282              
283             Returns an opaque ID value that can be used to remove this subscription by
284             calling C.
285              
286             =cut
287              
288 23         49 sub subscribe_event ( $self, $event, $callback )
  23         50  
289 23     23 1 637 {
  23         40  
  23         42  
290 23 50       87 $self->can_event( $event ) or croak "$self has no event $event";
291              
292 23   100     171 my $sublist = ( $self->{event_subs}->{$event} ||= [] );
293              
294 23         60 push @$sublist, $callback;
295              
296 23         52 my $ref = \@{$sublist}[$#$sublist]; # reference to last element
  23         54  
297 23         113 return $ref + 0; # force numeric context
298             }
299              
300             =head2 unsubscribe_event
301              
302             $obj->unsubscribe_event( $event, $id )
303              
304             Removes an event-handling callback previously registered with
305             C.
306              
307             =cut
308              
309 6         13 sub unsubscribe_event ( $self, $event, $id )
  6         19  
310 6     6 1 13 {
  6         10  
  6         7  
311 6 50       26 my $sublist = $self->{event_subs}->{$event} or return;
312              
313 6         10 my $index;
314 6         23 for( $index = 0; $index < @$sublist; $index++ ) {
315 4 50       8 last if \@{$sublist}[$index] + 0 == $id;
  4         19  
316             }
317              
318 6         27 splice @$sublist, $index, 1, ();
319             }
320              
321             =head2 watch_property
322              
323             $id = $obj->watch_property( $prop, %callbacks )
324              
325             Watches a named property for changes, registering a set of callback functions
326             to be invoked when the property changes in certain ways. The set of callbacks
327             required depends on the dimension of the property being watched.
328              
329             For all property types:
330              
331             $on_set->( $obj, $value )
332              
333             For hash properties:
334              
335             $on_add->( $obj, $key, $value )
336             $on_del->( $obj, $key )
337              
338             For queue properties:
339              
340             $on_push->( $obj, @values )
341             $on_shift->( $obj, $count )
342              
343             For array properties:
344              
345             $on_push->( $obj, @values )
346             $on_shift->( $obj, $count )
347             $on_splice->( $obj, $index, $count, @values )
348             $on_move->( $obj, $index, $delta )
349              
350             For objset properties:
351              
352             $on_add->( $obj, $added_object )
353             $on_del->( $obj, $deleted_object_id )
354              
355             Alternatively, a single callback may be installed that is invoked after any
356             change of the property, being passed the new value entirely:
357              
358             $on_updated->( $obj, $value )
359              
360             Returns an opaque ID value that can be used to remove this watch by calling
361             C.
362              
363             =cut
364              
365 47         89 sub watch_property ( $self, $prop, %callbacks )
  47         80  
366 47     47 1 4039 {
  47         106  
  47         67  
367 47 50       132 my $pdef = $self->can_property( $prop ) or croak "$self has no property $prop";
368              
369 47         108 my $callbacks = {};
370 47         64 my $on_updated;
371              
372 47 100       130 if( $callbacks{on_updated} ) {
373 4         9 $on_updated = delete $callbacks{on_updated};
374 4 50       12 ref $on_updated eq "CODE" or croak "Expected 'on_updated' to be a CODE ref";
375 4 50       8 keys %callbacks and croak "Expected no key other than 'on_updated'";
376 4         9 $callbacks->{on_updated} = $on_updated;
377             }
378             else {
379 43         69 foreach my $name ( @{ CHANGETYPES->{$pdef->dimension} } ) {
  43         122  
380 121 50       422 ref( $callbacks->{$name} = delete $callbacks{$name} ) eq "CODE"
381             or croak "Expected '$name' as a CODE ref";
382             }
383             }
384              
385 47         198 my $watchlist = $self->{properties}->{$prop}->callbacks;
386              
387 47         700 push @$watchlist, $callbacks;
388              
389 47 100       119 $on_updated->( $self, $self->{properties}->{$prop}->value ) if $on_updated;
390              
391 47         103 my $ref = \@{$watchlist}[$#$watchlist]; # reference to last element
  47         100  
392 47         165 return $ref + 0; # force numeric context
393             }
394              
395             =head2 unwatch_property
396              
397             $obj->unwatch_property( $prop, $id )
398              
399             Removes the set of callback functions previously registered with
400             C.
401              
402             =cut
403              
404 16         21 sub unwatch_property ( $self, $prop, $id )
  16         22  
405 16     16 1 24 {
  16         24  
  16         20  
406 16 50       50 my $watchlist = $self->{properties}->{$prop}->callbacks or return;
407              
408 16         96 my $index;
409 16         42 for( $index = 0; $index < @$watchlist; $index++ ) {
410 14 50       31 last if \@{$watchlist}[$index] + 0 == $id;
  14         45  
411             }
412              
413 16         75 splice @$watchlist, $index, 1, ();
414             }
415              
416             ### Message handling
417              
418 3         4 sub handle_request_CALL ( $self, $ctx, $message )
  3         6  
419 3     3 0 6 {
  3         6  
  3         4  
420 3         10 my $method = $message->unpack_str();
421              
422 3 50       72 my $mdef = $self->can_method( $method ) or die "Object cannot respond to method $method\n";
423              
424 3         11 my $m = "method_$method";
425 3 50       17 $self->can( $m ) or die "Object cannot run method $method\n";
426              
427 3         11 my @args = map { $_->unpack_value( $message ) } $mdef->argtypes;
  4         13  
428              
429 3         54 my $result = $self->$m( $ctx, @args );
430              
431 3         53 my $response = Tangence::Message->new( $ctx->stream, MSG_RESULT );
432 3 100       10 $mdef->ret->pack_value( $response, $result ) if $mdef->ret;
433              
434 3         14 return $response;
435             }
436              
437 2         4 sub generate_message_EVENT ( $self, $conn, $event, @args )
  2         4  
  2         3  
438 2     2 0 5 {
  2         5  
  2         3  
439 2 50       8 my $edef = $self->can_event( $event ) or die "Object cannot respond to event $event";
440              
441 2         12 my $response = Tangence::Message->new( $conn, MSG_EVENT )
442             ->pack_int( $self->id )
443             ->pack_str( $event );
444              
445 2         8 my @argtypes = $edef->argtypes;
446 2         14 $argtypes[$_]->pack_value( $response, $args[$_] ) for 0..$#argtypes;
447              
448 2         7 return $response;
449             }
450              
451 4         7 sub handle_request_GETPROP ( $self, $ctx, $message )
  4         17  
452 4     4 0 9 {
  4         8  
  4         8  
453 4         13 my $prop = $message->unpack_str();
454              
455 4 50       88 my $pdef = $self->can_property( $prop ) or die "Object does not have property $prop";
456              
457 4         16 my $m = "get_prop_$prop";
458 4 50       26 $self->can( $m ) or die "Object cannot get property $prop\n";
459              
460 4         18 my $result = $self->$m();
461              
462 4         42 my $response = Tangence::Message->new( $ctx->stream, MSG_RESULT );
463 4         26 $pdef->overall_type->pack_value( $response, $result );
464              
465 4         17 return $response;
466             }
467              
468 4         6 sub handle_request_GETPROPELEM ( $self, $ctx, $message )
  4         5  
469 4     4 0 9 {
  4         7  
  4         7  
470 4         20 my $prop = $message->unpack_str();
471              
472 4 50       83 my $pdef = $self->can_property( $prop ) or die "Object does not have property $prop";
473 4         16 my $dim = $pdef->dimension;
474              
475 4         11 my $m = "get_prop_$prop";
476 4 50       24 $self->can( $m ) or die "Object cannot get property $prop\n";
477              
478 4         7 my $result;
479             match( $dim : == ) {
480             case( DIM_QUEUE ), case( DIM_ARRAY ) {
481 2         8 my $idx = $message->unpack_int();
482 2         11 $result = $self->$m()->[$idx];
483             }
484             case( DIM_HASH ) {
485 2         7 my $key = $message->unpack_str();
486 2         53 $result = $self->$m()->{$key};
487             }
488 4 100 66     25 default {
    50          
489 0         0 die "Property $prop cannot fetch elements";
490             }
491             }
492              
493 4         43 my $response = Tangence::Message->new( $ctx->stream, MSG_RESULT );
494 4         15 $pdef->type->pack_value( $response, $result );
495              
496 4         16 return $response;
497             }
498              
499 8         14 sub handle_request_SETPROP ( $self, $ctx, $message )
  8         12  
500 8     8 0 14 {
  8         12  
  8         11  
501 8         21 my $prop = $message->unpack_str();
502              
503 8 50       220 my $pdef = $self->can_property( $prop ) or die "Object does not have property $prop\n";
504              
505 8         29 my $value = $pdef->overall_type->unpack_value( $message );
506              
507 7         24 my $m = "set_prop_$prop";
508 7 50       51 $self->can( $m ) or die "Object cannot set property $prop\n";
509              
510 7         32 $self->$m( $value );
511              
512 7         37 return Tangence::Message->new( $self, MSG_OK );
513             }
514              
515 45         73 sub generate_message_UPDATE ( $self, $conn, $prop, $how, @args )
  45         60  
  45         73  
  45         60  
516 45     45 0 83 {
  45         88  
  45         55  
517 45 50       101 my $pdef = $self->can_property( $prop ) or die "Object does not have property $prop\n";
518 45         142 my $dim = $pdef->dimension;
519              
520 45         240 my $message = Tangence::Message->new( $conn, MSG_UPDATE )
521             ->pack_int( $self->id )
522             ->pack_str( $prop );
523 45         182 TYPE_U8->pack_value( $message, $how );
524              
525 45         109 my $dimname = DIMNAMES->[$dim];
526 45 100       277 if( $how == CHANGE_SET ) {
    50          
527 22         59 my ( $value ) = @args;
528 22         73 $pdef->overall_type->pack_value( $message, $value );
529             }
530             elsif( my $code = $self->can( "_generate_message_UPDATE_$dimname" ) ) {
531 23         69 $code->( $self, $message, $how, $pdef, @args );
532             }
533             else {
534 0         0 croak "Unrecognised property dimension $dim for $prop";
535             }
536              
537 45         157 return $message;
538             }
539              
540 0         0 sub _generate_message_UPDATE_scalar ( $self, $message, $how, $pdef, @args )
  0         0  
  0         0  
  0         0  
541 0     0   0 {
  0         0  
  0         0  
542 0         0 croak "Change type $how is not valid for a scalar property";
543             }
544              
545 6         9 sub _generate_message_UPDATE_hash ( $self, $message, $how, $pdef, @args )
  6         10  
  6         16  
  6         9  
546 6     6   9 {
  6         9  
  6         10  
547             match( $how : == ) {
548             case( CHANGE_ADD ) {
549 4         10 my ( $key, $value ) = @args;
550 4         12 $message->pack_str( $key );
551 4         14 $pdef->type->pack_value( $message, $value );
552             }
553             case( CHANGE_DEL ) {
554 2         7 my ( $key ) = @args;
555 2         8 $message->pack_str( $key );
556             }
557 6 100       51 default {
    50          
558 0         0 croak "Change type $how is not valid for a hash property";
559             }
560             }
561             }
562              
563 6         13 sub _generate_message_UPDATE_queue ( $self, $message, $how, $pdef, @args )
  6         10  
  6         8  
  6         10  
564 6     6   9 {
  6         11  
  6         6  
565             match( $how : == ) {
566             case( CHANGE_PUSH ) {
567 3         20 $message->pack_all_sametype( $pdef->type, @args );
568             }
569             case( CHANGE_SHIFT ) {
570 3         8 my ( $count ) = @args;
571 3         9 $message->pack_int( $count );
572             }
573 6 100       26 default {
    50          
574 0         0 croak "Change type $how is not valid for a queue property";
575             }
576             }
577             }
578              
579 9         14 sub _generate_message_UPDATE_array ( $self, $message, $how, $pdef, @args )
  9         12  
  9         17  
  9         12  
580 9     9   14 {
  9         15  
  9         14  
581             match( $how : == ) {
582             case( CHANGE_PUSH ) {
583 3         13 $message->pack_all_sametype( $pdef->type, @args );
584             }
585             case( CHANGE_SHIFT ) {
586 1         3 my ( $count ) = @args;
587 1         3 $message->pack_int( $count );
588             }
589             case( CHANGE_SPLICE ) {
590 2         8 my ( $start, $count, @values ) = @args;
591 2         8 $message->pack_int( $start );
592 2         22 $message->pack_int( $count );
593 2         10 $message->pack_all_sametype( $pdef->type, @values );
594             }
595             case( CHANGE_MOVE ) {
596 3         10 my ( $index, $delta ) = @args;
597 3         11 $message->pack_int( $index );
598 3         12 $message->pack_int( $delta );
599             }
600 9 100       39 default {
    100          
    100          
    50          
601 0         0 croak "Change type $how is not valid for an array property";
602             }
603             }
604             }
605              
606 2         4 sub _generate_message_UPDATE_objset ( $self, $message, $how, $pdef, @args )
  2         4  
  2         3  
  2         4  
607 2     2   3 {
  2         4  
  2         4  
608             match( $how : == ) {
609             case( CHANGE_ADD ) {
610 1         3 my ( $value ) = @args;
611 1         4 $pdef->type->pack_value( $message, $value );
612             }
613             case( CHANGE_DEL ) {
614 1         3 my ( $id ) = @args;
615 1         3 $message->pack_int( $id );
616             }
617 2 100       16 default {
    50          
618 0           croak "Change type $how is not valid for an objset property";
619             }
620             }
621             }
622              
623             =head1 AUTHOR
624              
625             Paul Evans
626              
627             =cut
628              
629             0x55AA;