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-2016 -- leonerd@leonerd.org.uk
5              
6 14     14   151 use v5.26;
  14         39  
7 14     14   62 use Object::Pad 0.41;
  14         151  
  14         57  
8              
9             package Tangence::Property 0.29;
10              
11 14     14   1143 use warnings;
  14         22  
  14         362  
12 14     14   60 use base qw( Tangence::Meta::Property );
  14         45  
  14         4883  
13              
14 14     14   82 use Carp;
  14         20  
  14         630  
15              
16 14     14   67 use Tangence::Constants;
  14         22  
  14         2049  
17              
18             require Tangence::Type;
19              
20 14     14   5644 use Struct::Dumb;
  14         25106  
  14         57  
21             struct Instance => [qw( value callbacks cursors )];
22              
23             sub build_accessor
24             {
25 94     94 0 124 my $prop = shift;
26 94         157 my ( $subs ) = @_;
27              
28 94         200 my $pname = $prop->name;
29 94         203 my $dim = $prop->dimension;
30              
31             $subs->{"new_prop_$pname"} = sub {
32 117     117   151 my $self = shift;
        117      
        117      
        114      
        114      
33              
34 117         135 my $initial;
35              
36 117 100 66     759 if( my $code = $self->can( "init_prop_$pname" ) ) {
    100          
    100          
    100          
    50          
37 52         130 $initial = $code->( $self );
38             }
39             elsif( $dim == DIM_SCALAR ) {
40 28         107 $initial = $prop->type->default_value;
41             }
42             elsif( $dim == DIM_HASH ) {
43 11         31 $initial = {};
44             }
45             elsif( $dim == DIM_QUEUE or $dim == DIM_ARRAY ) {
46 13         35 $initial = [];
47             }
48             elsif( $dim == DIM_OBJSET ) {
49 13         41 $initial = {}; # these have hashes internally
50             }
51             else {
52 0         0 croak "Unrecognised dimension $dim for property $pname";
53             }
54              
55 117         393 $self->{properties}->{$pname} = Instance( $initial, [], [] );
56 94         487 };
57              
58             $subs->{"get_prop_$pname"} = sub {
59 96     213   4969 my $self = shift;
        306      
60 96         300 return $self->{properties}->{$pname}->value;
61 94         307 };
62              
63             $subs->{"set_prop_$pname"} = sub {
64 33     337   6988 my $self = shift;
        368      
65 33         64 my ( $newval ) = @_;
66 33         111 $self->{properties}->{$pname}->value = $newval;
67 33         210 my $cbs = $self->{properties}->{$pname}->callbacks;
68             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
69 33 100       240 : $_->{on_set}->( $self, $newval ) for @$cbs;
70 94         404 };
71              
72 94         176 my $dimname = DIMNAMES->[$dim];
73 94 50       439 if( my $code = __PACKAGE__->can( "_accessor_for_$dimname" ) ) {
74 94         216 $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   38 my $prop = shift;
89 22         51 my ( $subs, $pname ) = @_;
90              
91             $subs->{"add_prop_$pname"} = sub {
92 31     63   3025 my $self = shift;
        92      
93 31         73 my ( $key, $value ) = @_;
94 31         104 $self->{properties}->{$pname}->value->{$key} = $value;
95 31         225 my $cbs = $self->{properties}->{$pname}->callbacks;
96             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
97 31 100       200 : $_->{on_add}->( $self, $key, $value ) for @$cbs;
98 22         112 };
99              
100             $subs->{"del_prop_$pname"} = sub {
101 5     5   2231 my $self = shift;
        10      
102 5         12 my ( $key ) = @_;
103 5         25 delete $self->{properties}->{$pname}->value->{$key};
104 5         42 my $cbs = $self->{properties}->{$pname}->callbacks;
105             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
106 5 100       39 : $_->{on_del}->( $self, $key ) for @$cbs;
107 22         135 };
108             }
109              
110             sub _accessor_for_queue
111             {
112 10     10   22 my $prop = shift;
113 10         28 my ( $subs, $pname ) = @_;
114              
115             $subs->{"push_prop_$pname"} = sub {
116 4     4   2320 my $self = shift;
117 4         12 my @values = @_;
118 4         7 push @{ $self->{properties}->{$pname}->value }, @values;
  4         13  
119 4         33 my $cbs = $self->{properties}->{$pname}->callbacks;
120             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
121 4 100       31 : $_->{on_push}->( $self, @values ) for @$cbs;
122 10         54 };
123              
124             $subs->{"shift_prop_$pname"} = sub {
125 5     9   3309 my $self = shift;
126 5         12 my ( $count ) = @_;
127 5 100       15 $count = 1 unless @_;
128 5         9 splice @{ $self->{properties}->{$pname}->value }, 0, $count, ();
  5         15  
129 5         48 my $cbs = $self->{properties}->{$pname}->callbacks;
130             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
131 5 100       49 : $_->{on_shift}->( $self, $count ) for @$cbs;
132 5         42 my $cursors = $self->{properties}->{$pname}->cursors;
133 5         36 $_->idx -= $count for @$cursors;
134 10         66 };
135              
136             $subs->{"cursor_prop_$pname"} = sub {
137 6     11   10 my $self = shift;
138 6         17 my ( $from ) = @_;
139             my $idx = $from == CUSR_FIRST ? 0 :
140 6 50       25 $from == CUSR_LAST ? scalar @{ $self->{properties}->{$pname}->value } :
  1 100       4  
141             die "Unrecognised from";
142 6   50     32 my $cursors = $self->{properties}->{$pname}->cursors ||= [];
143 6         60 push @$cursors, my $cursor = Tangence::Property::_Cursor->new( $self->{properties}->{$pname}->value, $prop, $idx );
144 6         18 return $cursor;
145 10         55 };
146              
147             $subs->{"uncursor_prop_$pname"} = sub {
148 6     12   58 my $self = shift;
149 6         13 my ( $cursor ) = @_;
150 6 50       32 my $cursors = $self->{properties}->{$pname}->cursors or return;
151 6         42 @$cursors = grep { $_ != $cursor } @$cursors;
  6         45  
152 10         64 };
153             }
154              
155             sub _accessor_for_array
156             {
157 20     26   34 my $prop = shift;
158 20         44 my ( $subs, $pname ) = @_;
159              
160             $subs->{"push_prop_$pname"} = sub {
161 4     4   1133 my $self = shift;
162 4         17 my @values = @_;
163 4         9 push @{ $self->{properties}->{$pname}->value }, @values;
  4         13  
164 4         29 my $cbs = $self->{properties}->{$pname}->callbacks;
165             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
166 4 100       30 : $_->{on_push}->( $self, @values ) for @$cbs;
167 20         131 };
168              
169             $subs->{"shift_prop_$pname"} = sub {
170 3     7   2131 my $self = shift;
171 3         6 my ( $count ) = @_;
172 3 100       9 $count = 1 unless @_;
173 3         5 splice @{ $self->{properties}->{$pname}->value }, 0, $count, ();
  3         12  
174 3         20 my $cbs = $self->{properties}->{$pname}->callbacks;
175             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
176 3 100       23 : $_->{on_shift}->( $self, $count ) for @$cbs;
177 20         114 };
178              
179             $subs->{"splice_prop_$pname"} = sub {
180 4     11   3011 my $self = shift;
181 4         10 my ( $index, $count, @values ) = @_;
182 4         7 splice @{ $self->{properties}->{$pname}->value }, $index, $count, @values;
  4         13  
183 4         31 my $cbs = $self->{properties}->{$pname}->callbacks;
184             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
185 4 100       29 : $_->{on_splice}->( $self, $index, $count, @values ) for @$cbs;
186 20         115 };
187              
188             $subs->{"move_prop_$pname"} = sub {
189 6     13   2210 my $self = shift;
190 6         9 my ( $index, $delta ) = @_;
191 6 50       17 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         19 my $cache = $self->{properties}->{$pname}->value;
196 6 100       41 if( abs($delta) == 1 ) {
197 1         5 @{$cache}[$index,$index+$delta] = @{$cache}[$index+$delta,$index];
  1         2  
  1         2  
198             }
199             else {
200 5         11 my $elem = splice @$cache, $index, 1, ();
201 5         15 splice @$cache, $index + $delta, 0, ( $elem );
202             }
203 6         16 my $cbs = $self->{properties}->{$pname}->callbacks;
204             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
205 6 100       39 : $_->{on_move}->( $self, $index, $delta ) for @$cbs;
206 20         139 };
207             }
208              
209             sub _accessor_for_objset
210             {
211 10     20   36 my $prop = shift;
212 10         34 my ( $subs, $pname ) = @_;
213              
214             # Different get and set methods
215             $subs->{"get_prop_$pname"} = sub {
216 5     11   942 my $self = shift;
217 5         8 return [ values %{ $self->{properties}->{$pname}->value } ];
  5         14  
218 10         154 };
219              
220             $subs->{"set_prop_$pname"} = sub {
221 3     8   6 my $self = shift;
222 3         7 my ( $newval ) = @_;
223 3         13 $self->{properties}->{$pname}->value = $newval;
224 3         26 my $cbs = $self->{properties}->{$pname}->callbacks;
225             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
226 3 50       27 : $_->{on_set}->( $self, [ values %$newval ] ) for @$cbs;
227 10         98 };
228              
229             $subs->{"add_prop_$pname"} = sub {
230 2     5   5 my $self = shift;
231 2         4 my ( $obj ) = @_;
232 2         8 $self->{properties}->{$pname}->value->{$obj->id} = $obj;
233 2         8 my $cbs = $self->{properties}->{$pname}->callbacks;
234             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
235 2 50       18 : $_->{on_add}->( $self, $obj ) for @$cbs;
236 10         59 };
237              
238             $subs->{"del_prop_$pname"} = sub {
239 2     4   1341 my $self = shift;
240 2         5 my ( $obj_or_id ) = @_;
241 2 50       18 my $id = ref $obj_or_id ? $obj_or_id->id : $obj_or_id;
242 2         8 delete $self->{properties}->{$pname}->value->{$id};
243 2         17 my $cbs = $self->{properties}->{$pname}->callbacks;
244             $_->{on_updated} ? $_->{on_updated}->( $self, $self->{properties}->{$pname}->value )
245 2 50       17 : $_->{on_del}->( $self, $id ) for @$cbs;
246 10         89 };
247             }
248              
249             sub make_type
250             {
251 31     33 0 46 shift;
252 31         108 return Tangence::Type->make( @_ );
253             }
254              
255             class # hide from CPAN
256             Tangence::Property::_Cursor
257             {
258 14     14   35028 use Carp;
  14         27  
  14         933  
259              
260 14     14   100 use Tangence::Constants;
  14         30  
  14         11103  
261              
262 0     0   0 has $queue :param :reader;
  0         0  
263 6     6   48 has $prop :param :reader;
  6         26  
264 2     2   4 has $idx :param :mutator;
  2         7  
265              
266 6         12 sub BUILDARGS ( $class, $queue, $prop, $idx )
  6         14  
  6         9  
267 6     6   68 {
  6         9  
  6         8  
268 6         48 return ( queue => $queue, prop => $prop, idx => $idx );
269             }
270              
271             method handle_request_CUSR_NEXT
272 12     12   94 {
273 12         22 my ( $ctx, $message ) = @_;
274              
275 12         37 my $direction = $message->unpack_int();
276 12         28 my $count = $message->unpack_int();
277              
278 12         16 my $start_idx = $idx;
279              
280 12 100       36 if( $direction == CUSR_FWD ) {
    50          
281 7 100       55 $count = scalar @$queue - $idx if $count > scalar @$queue - $idx;
282              
283 7         12 $idx += $count;
284             }
285             elsif( $direction == CUSR_BACK ) {
286 5 100       16 $count = $idx if $count > $idx;
287 5         11 $idx -= $count;
288 5         10 $start_idx = $idx;
289             }
290             else {
291 0         0 return $ctx->responderr( "Unrecognised cursor direction $direction" );
292             }
293              
294 12         28 my @result = @{$queue}[$start_idx .. $start_idx + $count - 1];
  12         31  
295              
296 12         39 $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;