File Coverage

blib/lib/Tangence/Property.pm
Criterion Covered Total %
statement 188 193 97.4
branch 50 60 83.3
condition 3 5 60.0
subroutine 45 46 97.8
pod 0 2 0.0
total 286 306 93.4


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, 2013-2022 -- leonerd@leonerd.org.uk
5              
6 14     14   172 use v5.26;
  14         56  
7 14     14   116 use Object::Pad 0.66;
  14         187  
  14         72  
8              
9             package Tangence::Property 0.30;
10              
11 14     14   1595 use warnings;
  14         29  
  14         435  
12 14     14   71 use base qw( Tangence::Meta::Property );
  14         26  
  14         6120  
13              
14 14     14   99 use Carp;
  14         29  
  14         749  
15              
16 14     14   81 use Tangence::Constants;
  14         30  
  14         2350  
17              
18             require Tangence::Type;
19              
20 14     14   6642 use Struct::Dumb;
  14         39760  
  14         63  
21             struct Instance => [qw( value callbacks cursors )];
22              
23             sub build_accessor
24             {
25 94     94 0 155 my $prop = shift;
26 94         162 my ( $subs ) = @_;
27              
28 94         273 my $pname = $prop->name;
29 94         257 my $dim = $prop->dimension;
30              
31             $subs->{"new_prop_$pname"} = sub {
32 117     117   184 my $self = shift;
        117      
        117      
        114      
        114      
33              
34 117         153 my $initial;
35              
36 117 100 66     969 if( my $code = $self->can( "init_prop_$pname" ) ) {
    100          
    100          
    100          
    50          
37 52         179 $initial = $code->( $self );
38             }
39             elsif( $dim == DIM_SCALAR ) {
40 28         146 $initial = $prop->type->default_value;
41             }
42             elsif( $dim == DIM_HASH ) {
43 11         35 $initial = {};
44             }
45             elsif( $dim == DIM_QUEUE or $dim == DIM_ARRAY ) {
46 13         39 $initial = [];
47             }
48             elsif( $dim == DIM_OBJSET ) {
49 13         35 $initial = {}; # these have hashes internally
50             }
51             else {
52 0         0 croak "Unrecognised dimension $dim for property $pname";
53             }
54              
55 117         505 $self->{properties}->{$pname} = Instance( $initial, [], [] );
56 94         554 };
57              
58             $subs->{"get_prop_$pname"} = sub {
59 96     213   6883 my $self = shift;
        306      
60 96         348 return $self->{properties}->{$pname}->value;
61 94         429 };
62              
63             $subs->{"set_prop_$pname"} = sub {
64 33     337   9634 my $self = shift;
        368      
65 33         72 my ( $newval ) = @_;
66 33         129 $self->{properties}->{$pname}->value = $newval;
67 33         238 my $cbs = $self->{properties}->{$pname}->callbacks;
68             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
69 33 100       243 : $_->{on_set}->( $self, $newval ) for @$cbs;
70 94         487 };
71              
72 94         225 my $dimname = DIMNAMES->[$dim];
73 94 50       512 if( my $code = __PACKAGE__->can( "_accessor_for_$dimname" ) ) {
74 94         269 $code->( $prop, $subs, $pname );
75             }
76             else {
77 0         0 croak "Unrecognised property dimension $dim for $pname";
78             }
79             }
80              
81             sub _accessor_for_scalar
82       305     {
83             # Nothing needed
84             }
85              
86             sub _accessor_for_hash
87             {
88 22     180   49 my $prop = shift;
89 22         70 my ( $subs, $pname ) = @_;
90              
91             $subs->{"add_prop_$pname"} = sub {
92 31     63   4610 my $self = shift;
        92      
93 31         87 my ( $key, $value ) = @_;
94 31         124 $self->{properties}->{$pname}->value->{$key} = $value;
95 31         264 my $cbs = $self->{properties}->{$pname}->callbacks;
96             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
97 31 100       242 : $_->{on_add}->( $self, $key, $value ) for @$cbs;
98 22         128 };
99              
100             $subs->{"del_prop_$pname"} = sub {
101 5     5   3286 my $self = shift;
        10      
102 5         17 my ( $key ) = @_;
103 5         26 delete $self->{properties}->{$pname}->value->{$key};
104 5         51 my $cbs = $self->{properties}->{$pname}->callbacks;
105             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
106 5 100       50 : $_->{on_del}->( $self, $key ) for @$cbs;
107 22         179 };
108             }
109              
110             sub _accessor_for_queue
111             {
112 10     10   32 my $prop = shift;
113 10         35 my ( $subs, $pname ) = @_;
114              
115             $subs->{"push_prop_$pname"} = sub {
116 4     4   3281 my $self = shift;
117 4         14 my @values = @_;
118 4         7 push @{ $self->{properties}->{$pname}->value }, @values;
  4         19  
119 4         44 my $cbs = $self->{properties}->{$pname}->callbacks;
120             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
121 4 100       35 : $_->{on_push}->( $self, @values ) for @$cbs;
122 10         88 };
123              
124             $subs->{"shift_prop_$pname"} = sub {
125 5     9   4809 my $self = shift;
126 5         14 my ( $count ) = @_;
127 5 100       20 $count = 1 unless @_;
128 5         8 splice @{ $self->{properties}->{$pname}->value }, 0, $count, ();
  5         29  
129 5         47 my $cbs = $self->{properties}->{$pname}->callbacks;
130             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
131 5 100       45 : $_->{on_shift}->( $self, $count ) for @$cbs;
132 5         40 my $cursors = $self->{properties}->{$pname}->cursors;
133 5         40 $_->idx -= $count for @$cursors;
134 10         76 };
135              
136             $subs->{"cursor_prop_$pname"} = sub {
137 6     11   15 my $self = shift;
138 6         15 my ( $from ) = @_;
139             my $idx = $from == CUSR_FIRST ? 0 :
140 6 50       36 $from == CUSR_LAST ? scalar @{ $self->{properties}->{$pname}->value } :
  1 100       4  
141             die "Unrecognised from";
142 6   50     34 my $cursors = $self->{properties}->{$pname}->cursors ||= [];
143 6         75 push @$cursors, my $cursor = Tangence::Property::_Cursor->new( $self->{properties}->{$pname}->value, $prop, $idx );
144 6         19 return $cursor;
145 10         80 };
146              
147             $subs->{"uncursor_prop_$pname"} = sub {
148 6     12   76 my $self = shift;
149 6         16 my ( $cursor ) = @_;
150 6 50       21 my $cursors = $self->{properties}->{$pname}->cursors or return;
151 6         57 @$cursors = grep { $_ != $cursor } @$cursors;
  6         48  
152 10         94 };
153             }
154              
155             sub _accessor_for_array
156             {
157 20     26   53 my $prop = shift;
158 20         61 my ( $subs, $pname ) = @_;
159              
160             $subs->{"push_prop_$pname"} = sub {
161 4     4   1770 my $self = shift;
162 4         14 my @values = @_;
163 4         10 push @{ $self->{properties}->{$pname}->value }, @values;
  4         22  
164 4         37 my $cbs = $self->{properties}->{$pname}->callbacks;
165             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
166 4 100       39 : $_->{on_push}->( $self, @values ) for @$cbs;
167 20         124 };
168              
169             $subs->{"shift_prop_$pname"} = sub {
170 3     7   3230 my $self = shift;
171 3         8 my ( $count ) = @_;
172 3 100       13 $count = 1 unless @_;
173 3         16 splice @{ $self->{properties}->{$pname}->value }, 0, $count, ();
  3         14  
174 3         33 my $cbs = $self->{properties}->{$pname}->callbacks;
175             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
176 3 100       28 : $_->{on_shift}->( $self, $count ) for @$cbs;
177 20         125 };
178              
179             $subs->{"splice_prop_$pname"} = sub {
180 4     11   4564 my $self = shift;
181 4         15 my ( $index, $count, @values ) = @_;
182 4         8 splice @{ $self->{properties}->{$pname}->value }, $index, $count, @values;
  4         18  
183 4         39 my $cbs = $self->{properties}->{$pname}->callbacks;
184             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
185 4 100       43 : $_->{on_splice}->( $self, $index, $count, @values ) for @$cbs;
186 20         113 };
187              
188             $subs->{"move_prop_$pname"} = sub {
189 6     13   3303 my $self = shift;
190 6         19 my ( $index, $delta ) = @_;
191 6 50       19 return if $delta == 0;
192             # it turns out that exchanging neighbours is quicker by list assignment,
193             # but other times it's generally best to use splice() to extract then
194             # insert
195 6         25 my $cache = $self->{properties}->{$pname}->value;
196 6 100       54 if( abs($delta) == 1 ) {
197 1         5 @{$cache}[$index,$index+$delta] = @{$cache}[$index+$delta,$index];
  1         3  
  1         2  
198             }
199             else {
200 5         16 my $elem = splice @$cache, $index, 1, ();
201 5         21 splice @$cache, $index + $delta, 0, ( $elem );
202             }
203 6         20 my $cbs = $self->{properties}->{$pname}->callbacks;
204             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
205 6 100       53 : $_->{on_move}->( $self, $index, $delta ) for @$cbs;
206 20         189 };
207             }
208              
209             sub _accessor_for_objset
210             {
211 10     20   34 my $prop = shift;
212 10         33 my ( $subs, $pname ) = @_;
213              
214             # Different get and set methods
215             $subs->{"get_prop_$pname"} = sub {
216 5     11   1391 my $self = shift;
217 5         12 return [ values %{ $self->{properties}->{$pname}->value } ];
  5         20  
218 10         95 };
219              
220             $subs->{"set_prop_$pname"} = sub {
221 3     8   9 my $self = shift;
222 3         9 my ( $newval ) = @_;
223 3         18 $self->{properties}->{$pname}->value = $newval;
224 3         28 my $cbs = $self->{properties}->{$pname}->callbacks;
225             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
226 3 50       33 : $_->{on_set}->( $self, [ values %$newval ] ) for @$cbs;
227 10         113 };
228              
229             $subs->{"add_prop_$pname"} = sub {
230 2     5   6 my $self = shift;
231 2         6 my ( $obj ) = @_;
232 2         11 $self->{properties}->{$pname}->value->{$obj->id} = $obj;
233 2         11 my $cbs = $self->{properties}->{$pname}->callbacks;
234             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
235 2 50       23 : $_->{on_add}->( $self, $obj ) for @$cbs;
236 10         67 };
237              
238             $subs->{"del_prop_$pname"} = sub {
239 2     4   1995 my $self = shift;
240 2         6 my ( $obj_or_id ) = @_;
241 2 50       12 my $id = ref $obj_or_id ? $obj_or_id->id : $obj_or_id;
242 2         11 delete $self->{properties}->{$pname}->value->{$id};
243 2         20 my $cbs = $self->{properties}->{$pname}->callbacks;
244             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
245 2 50       19 : $_->{on_del}->( $self, $id ) for @$cbs;
246 10         95 };
247             }
248              
249             sub make_type
250             {
251 31     33 0 57 shift;
252 31         131 return Tangence::Type->make( @_ );
253             }
254              
255             class # hide from CPAN
256             Tangence::Property::_Cursor
257             {
258 14     14   44451 use Carp;
  14         32  
  14         1116  
259              
260 14     14   122 use Tangence::Constants;
  14         30  
  14         13451  
261              
262 0     0   0 field $queue :param :reader;
  0         0  
263 6     6   54 field $prop :param :reader;
  6         32  
264 2     2   6 field $idx :param :mutator;
  2         20  
265              
266 6         12 sub BUILDARGS ( $class, $queue, $prop, $idx )
  6         7  
  6         22  
267 6     6   78 {
  6         17  
  6         10  
268 6         69 return ( queue => $queue, prop => $prop, idx => $idx );
269             }
270              
271             method handle_request_CUSR_NEXT
272 12     12   108 {
273 12         26 my ( $ctx, $message ) = @_;
274              
275 12         27 my $direction = $message->unpack_int();
276 12         41 my $count = $message->unpack_int();
277              
278 12         21 my $start_idx = $idx;
279              
280 12 100       43 if( $direction == CUSR_FWD ) {
    50          
281 7 100       48 $count = scalar @$queue - $idx if $count > scalar @$queue - $idx;
282              
283 7         27 $idx += $count;
284             }
285             elsif( $direction == CUSR_BACK ) {
286 5 100       23 $count = $idx if $count > $idx;
287 5         10 $idx -= $count;
288 5         9 $start_idx = $idx;
289             }
290             else {
291 0         0 return $ctx->responderr( "Unrecognised cursor direction $direction" );
292             }
293              
294 12         34 my @result = @{$queue}[$start_idx .. $start_idx + $count - 1];
  12         39  
295              
296 12         42 $ctx->respond( Tangence::Message->new( $ctx->stream, MSG_CUSR_RESULT )
297             ->pack_int( $start_idx )
298             ->pack_all_sametype( $prop->type, @result )
299             );
300             }
301             }
302              
303             0x55AA;