File Coverage

blib/lib/Tangence/Property.pm
Criterion Covered Total %
statement 177 181 97.7
branch 43 52 82.6
condition 3 5 60.0
subroutine 45 46 97.8
pod 0 10 0.0
total 268 294 91.1


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