File Coverage

blib/lib/Games/3D/Thingy.pm
Criterion Covered Total %
statement 174 244 71.3
branch 47 86 54.6
condition 4 5 80.0
subroutine 32 41 78.0
pod 28 29 96.5
total 285 405 70.3


line stmt bran cond sub pod time code
1              
2             # Thingy - a base class for virtual and physical 3D objects
3              
4             package Games::3D::Thingy;
5              
6             # (C) by Tels
7              
8 5     5   30710 use strict;
  5         19  
  5         246  
9              
10             require Exporter;
11 5     5   28 use vars qw/@ISA $VERSION $AUTOLOAD/;
  5         9  
  5         642  
12             @ISA = qw/Exporter/;
13              
14 5         3273 use Games::3D::Signal qw/
15             STATE_OFF STATE_FLIP STATE_ON STATE_0
16             SIG_FLIP SIG_ACTIVATE SIG_DEACTIVATE
17             SIG_DIE SIG_NOW_0 SIG_KILLED
18             state_from_signal
19             signal_from_state signal_name
20 5     5   1606 /;
  5         20  
21              
22             sub DEBUG () { 0; }
23              
24             $VERSION = '0.04';
25              
26             ##############################################################################
27             # protected vars
28              
29             # each Thingy will get a unique ID, however, upon adding it to the world/level
30             # it will get a new ID, local and unique to that world. We could do away with
31             # this function here...
32             {
33             my $id = 1;
34 18     18 0 56 sub ID { return $id++;}
35             }
36              
37             ##############################################################################
38             # methods
39              
40             sub new
41             {
42             # create a new instance of a thingy
43 18     18 1 3273 my $class = shift;
44              
45 18         24 my $template;
46 18 100       61 $template = shift if ref($_[0]) =~ /::Template/;
47              
48 18         43 my $self = { id => ID() };
49 18         41 bless $self, $class;
50              
51 18         82 $self->{active} = 1;
52 18         58 $self->{_world} = undef; # not contained in anything yet
53            
54 18         32 $self->{outputs} = {};
55 18         41 $self->{inputs} = {};
56            
57 18         33 $self->{name} = $class;
58 18         82 $self->{name} =~ s/.*:://;
59 18         48 $self->{name} = ucfirst($self->{name});
60 18         72 $self->{name} .= ' #' . $self->{id};
61            
62 18         32 $self->{state} = 0; # current state
63              
64             # time when state change has to end. endtime is starttime + time_for_change,
65             # as defined in field 0 of 'states' below:
66 18         38 $self->{state_endtime} = 0; # disable change
67 18         29 $self->{state_target} = 0; # target state (from current)
68              
69             # example:
70 18         75 $self->{state_0} = [
71             1, # ms to change to this state
72             # example:
73             # 'light_r' => 0, # light off
74             # 'light_g' => 0, # light off
75             # 'light_b' => 0, # light off
76             # 'light_a' => 0, # light off
77             ];
78 18         45 $self->{state_1} = [
79             1,
80             # example:
81             # 'light_r' => 1.0, # light on
82             # 'light_g' => 1.0, # light on
83             # 'light_b' => 0, # light on
84             # 'light_a' => 1.0, # light on
85             ];
86            
87 18         29 $self->{visible} = 0; # invisible
88 18         43 $self->{think_time} = 0; # never think
89 18         27 $self->{next_think} = 0;
90              
91 18 100       49 $template->init_thing($self) if $template;
92              
93 18         57 $self->_init(@_);
94             }
95              
96             sub kill
97             {
98 1     1 1 2 my ($self,$src) = @_;
99              
100 1         5 $self->event($src,'kill');
101              
102             # send SIG_KILLED to all our links to announce our death
103 1         4 $self->output($self, SIG_KILLED);
104              
105             # remove all links from and to ourself
106 1         2 $self->unlink();
107              
108             # remove ourself from parent if necc.
109 1 50       3 $self->{_world}->unregister($self) if $self->{_world};
110              
111 1         2 undef;
112             }
113              
114             sub event
115             {
116             # when an event (frob, use, kill etc) occurs, this routine handles it
117 1     1 1 2 my ($self,$src,$event) = @_;
118              
119 1 50       5 &{$self->{"_event_$event"}}($self,$src) if $self->{"_event_$event"};
  0         0  
120             }
121              
122             sub AUTOLOAD
123             {
124             # when you do $self->name(), the AUTOLOAD steps in, checks that the
125             # attribute "name" exists, and constructs a little accessor method for it.
126             # This is then put into place and the next time round it will be called
127             # directly.
128              
129 3     3   14 my $func = $AUTOLOAD;
130 3         5 my $class = $func;
131 3         12 $func =~ s/.*:://; # remove package
132 3         13 $class =~ s/::[^:]+$//; # keep package
133 3 50       9 return if $func eq 'DESTROY'; # we have DESTROY, so not necc. here
134            
135             # print "autoload for $class $func\n";
136 5     5   32 no strict 'refs';
  5         12  
  5         3051  
137             # if (!$self->attr_exists($func))
138             # {
139             # require Carp; Carp::croak ("Attribute '$func' does not exist in $class");
140             # }
141 3         17 *{$class."::$func"} =
142             sub {
143 2     2   4 my $self = shift;
144 2 100       14 if (@_ > 0)
145             {
146             # more than one argument, need to modify
147 1         11 $self->{$func} = $_[0];
148             }
149 2         11 $self->{$func};
150 3         11 };
151 3         16 &$func; # call constructed accessor method using @_
152             }
153              
154             sub id
155             {
156 5     5 1 1144 my $self = shift;
157              
158 5         55 $self->{id};
159             }
160              
161             sub as_string
162             {
163 1     1 1 2 my $self = shift;
164              
165 1         5 my $txt = ref($self) . " {\n";
166 1         13 foreach my $k (sort keys %$self)
167             {
168 15 100       36 next if $k =~ /^_/; # skip internal keys
169 14         28 my $v = $self->{$k}; # get key
170 14 100       27 next if !defined $v; # skip empty
171 13 100       37 if (ref($v) eq 'HASH')
    100          
172             {
173 2 50       9 next if scalar keys %$v == 0;
174 0         0 $v = "{\n";
175 0         0 foreach my $key (sort keys %{$self->{$k}})
  0         0  
176             {
177 0         0 my $vi = $self->{$k}->{$key};
178 0 0       0 $vi = $vi->as_string() if ref($v);
179 0         0 $v .= " $key = $vi\n";
180             }
181 0         0 $v .= " }";
182             }
183             elsif (ref($v) eq 'ARRAY')
184             {
185 2 50       5 next if scalar @$v == 0;
186 2         4 $v = "[ ";
187 2         3 foreach my $vi (@{$self->{$k}})
  2         6  
188             {
189 2 50       4 $vi = $vi->as_string() if ref($v);
190 2         7 $v .= "$vi, ";
191             }
192 2         8 $v =~ /,\s$/; # remove last ,
193 2         3 $v .= "]";
194             }
195             else
196             {
197 9 100       28 $v = '"'.$v.'"' if $v =~ /[^a-z0-9_\.,='"+-]/;
198 9 50       20 next if $v eq '';
199             }
200 11         28 $txt .= " $k = $v\n";
201             }
202 1         12 $txt .= "}\n";
203             }
204              
205             sub new_flag
206             {
207 0     0 1 0 my ($self,$name,$value) = @_;
208              
209 0         0 $name =~ s/^-//; # -name => name
210              
211             # set the initial value
212 0 0       0 $self->{$name} = $value ? 1 : 0;
213              
214 0         0 my $class = ref($self);
215 0 0       0 return if defined *{$class."::is_$name"};
  0         0  
216              
217             # create an accessor method
218 5     5   40 no strict 'refs';
  5         12  
  5         580  
219 0         0 *{$class."::is_$name"} =
220             sub {
221 0     0   0 my $self = shift;
222 0 0       0 if (@_ > 0)
223             {
224             # more than one argument, need to modify
225 0 0       0 $self->{$name} = $_[0] ? 1 : 0;
226             }
227 0         0 $self->{$name};
228 0         0 };
229             }
230              
231 5     5   36 BEGIN { no warnings 'redefine'; }
  5     5   12  
  5         236  
  5         8948  
232              
233             sub _init
234             {
235 15     15   21 my $self = shift;
236 15         38 $self;
237             }
238              
239             sub container
240             {
241             # return the container this thing is inside or undef for none
242 0     0 1 0 my $self = shift;
243              
244 0         0 $self->{parent};
245             }
246              
247             sub insert
248             {
249             # insert thingy into a container
250 0     0 1 0 my $self = shift;
251 0         0 my $thingy = shift;
252              
253 0         0 $self->{contains}->{$thingy->{id}} = $thingy;
254 0         0 $self->_update_space();
255 0         0 $self;
256             }
257              
258             sub _update_space
259 0     0   0 {
260             }
261              
262             sub remove
263             {
264             # remove thing from ourself
265 0     0 1 0 my $self = shift;
266 0         0 my $thing = shift;
267              
268 0 0       0 if (ref $thing)
269             {
270 0         0 my $c = $self->{contains};
271 0 0       0 if (exists $c->{$thing->{id}})
272             {
273 0         0 delete $c->{$thing->{id}};
274 0         0 $self->_update_space();
275             }
276             }
277             else
278             {
279             # try to remove us from our container
280 0 0       0 $self->{parent}->remove($self) if (defined $self->{parent});
281             }
282             }
283              
284             sub name
285             {
286             # (set and) return the name of this thingy
287 6     6 1 18 my $self = shift;
288 6 50       20 if (defined $_[0])
289             {
290 0         0 $self->{name} = shift;
291             }
292 6         62 $self->{name};
293             }
294              
295             sub activate
296             {
297 5     5 1 10 my ($self) = shift;
298              
299 5         9 $self->{active} = 1;
300 5         50 1;
301             }
302              
303             sub deactivate
304             {
305 5     5 1 11 my ($self) = shift;
306              
307 5         9 $self->{active} = 0;
308 5         17 0;
309             }
310              
311             sub is_active
312             {
313 7     7 1 15 my ($self) = shift;
314              
315 7         35 $self->{active};
316             }
317              
318             sub state
319             {
320 34     34 1 946 my $self = shift;
321              
322             # if given a state change and we are active
323 34 100 66     120 if (defined $_[0] && $self->{active} == 1)
324             {
325 15         43 my $old_state = $self->{state};
326              
327             # initiate state change:
328 15         18 my $newstate;
329            
330 15 100       29 if ($_[0] == STATE_FLIP)
331             {
332 2 50       6 if ($self->{state} <= STATE_ON)
333             {
334 2         5 $newstate = STATE_ON - $self->{state};
335             }
336             else
337             {
338             # XXX TODO: on thingy with more than 2 states, flip is undefined
339 0         0 $newstate = STATE_0;
340             }
341             }
342             else
343             {
344 13         47 $newstate = $_[0];
345             }
346              
347 15 100       35 if ($self->{state} != $newstate)
348             {
349 12         11 print '# ', $self->name(),
350             " changes state from $self->{state} to $newstate\n" if DEBUG;
351              
352             # set the endtime for when the state change should be complete
353 12         12 my $now = 0;
354 12 50       24 $now = $self->{_world}->time() if $self->{_world};
355 12   100     48 $self->{state_endtime} = $now +
356             ($self->{"state_$newstate"}->[0] || 1); # avoid state changes
357             # that take no time
358 12         22 $self->{state_target} = $newstate;
359             # notifing our listeners will be done when the state change is complete
360             }
361             }
362 34         106 $self->{state};
363             }
364              
365             sub signal
366             {
367             # receive signal $sig from input $input, where $input is the sender's ID (not
368             # the link(s) relaying the signal). We ignore here the input. Links relay
369             # their input to their outputs (maybe, delayed , inverted etc), while other
370             # objects receive input, change state (or not) and then maybe output
371             # something.
372 11     11 1 21 my ($self,$input,$sig) = @_;
373              
374 11 100       15 my $id = $input; $id = $input->{id} if ref($id);
  11         22  
375 11         12 print "# ",$self->name()," received signal ",signal_name($sig),
376             " from $id\n" if DEBUG;
377              
378             # if asked to die, do so now
379 11 50       23 if ($sig == SIG_DIE)
380             {
381 0         0 $self->kill();
382 0         0 return;
383             }
384             # if asked to deactivate, do so now
385 11 50       21 if ($sig == SIG_ACTIVATE)
386             {
387 0         0 $self->{active} = 1;
388 0         0 return;
389             }
390 11 50       26 if ($sig == SIG_DEACTIVATE)
391             {
392 0         0 $self->{active} = 0;
393 0         0 return;
394             }
395             # set ourself to the new state, unless SIG_NOW_x (these are ignored)
396 11 100       41 $self->state(state_from_signal($sig)) if $sig <= SIG_NOW_0;
397             # relay incoming signals to outputs if neccessary
398 11         26 $self->output($input,$sig);
399             }
400              
401             sub inputs
402             {
403 2     2 1 7 my ($self) = @_;
404            
405 2         2 keys %{$self->{inputs}};
  2         10  
406             }
407              
408             sub outputs
409             {
410 0     0 1 0 my ($self) = @_;
411            
412 0         0 keys %{$self->{outputs}};
  0         0  
413             }
414              
415             sub add_input
416             {
417 7     7 1 9 my ($self,$src) = @_;
418            
419 7         19 $self->{inputs}->{$src->{id}} = $src;
420             }
421              
422             sub add_output
423             {
424 6     6 1 8 my ($self,$dst) = @_;
425              
426 6         17 $self->{outputs}->{$dst->{id}} = $dst;
427             }
428              
429             sub del_input
430             {
431 2     2 1 3 my ($self,$src) = @_;
432            
433 2         8 delete $self->{inputs}->{$src->{id}};
434             }
435              
436             sub del_output
437             {
438 1     1 1 2 my ($self,$dst) = @_;
439              
440 1         4 delete $self->{outputs}->{$dst->{id}};
441             }
442              
443             sub unlink
444             {
445             # unlink all inputs and outputs from ourself
446 2     2 1 4 my $self = shift;
447              
448 2         2 foreach my $out (keys %{$self->{outputs}})
  2         5  
449             {
450 2 50       15 $self->{outputs}->{$out}->del_input($self)
451             if ref($self->{outputs}->{$out});
452             }
453 2         3 foreach my $in (keys %{$self->{inputs}})
  2         5  
454             {
455 1 50       35 $self->{inputs}->{$in}->del_output($self)
456             if ref($self->{inputs}->{$in});
457             }
458 2         3 $self->{inputs} = {};
459 2         12 $self->{outputs} = {};
460 2         7 $self;
461             }
462              
463             sub output
464             {
465             # send a signal to all the outputs
466 32     32 1 51 my ($self,$source,$sig) = @_;
467              
468 32 100       68 $source = $source->{id} if ref($source);
469 32         41 my $out = $self->{outputs};
470 32         32 foreach my $id (keys %{$self->{outputs}})
  32         102  
471             {
472 16         46 $out->{$id}->signal($source,$sig); # sender id, signal
473             }
474             }
475              
476             sub link
477             {
478             # link us to another one by creating intermidiate link object and return
479             # link object
480 1     1 1 5 my ($self,$dst,$link) = @_;
481              
482 1         3 $self->{outputs}->{$link->{id}} = $link;
483 1         3 $link->add_output($dst); # from link to $dst
484 1         3 $dst->add_input($link);
485 1         2 $link->add_input($self); # from us to link
486 1         2 $link;
487             }
488              
489             sub update
490             {
491             # if thing is going from state A to state B, interpolate values based upon
492             # current time tick. If reached state B, disable interpolation, and send a
493             # signal. Return 1 if while still in transit, 0 if target state reached
494              
495 10     10 1 18 my ($self, $tick) = @_;
496              
497             # if the thingy is in between two state changes, interpolate between them
498 10 50       41 return if $self->{state_endtime} == 0; # no change neccessary
499            
500             # for all fields in the target state, interpolate them
501 10         23 my $s = "state_$self->{state_target}";
502 10 50       23 if (!exists $self->{$s})
503             {
504 0         0 $self->{$s} = [1];
505             }
506 10         13 my @states = @{$self->{$s}};
  10         25  
507              
508 10 50       28 if ($tick >= $self->{state_endtime}) # overdue
509             {
510             # simple set the fields, and disable the state change
511 10         9 print "# update($tick) caused change ",$self->name(),
512             " $self->{state} => $self->{state_target}\n" if DEBUG;
513              
514 10         15 $self->{state_endtime} = 0; # no further change
515 10         18 $self->{state} = $self->{state_target}; # reached target state
516             # send signal that state change is complete
517 10         11 print "# Sending signal ", signal_name(signal_from_state($self->{state})),
518             "\n" if DEBUG;
519 10         32 $self->output($self, signal_from_state($self->{state}));
520              
521 10         27 while (@states > 0)
522             {
523             # set a => 1 (f.i.)
524 10         24 $self->{$states[0]} = $states[1];
525 10         30 splice @states,0,2; # throw away first two entries
526             }
527 10         24 return 0; # no more changes
528             }
529            
530 0         0 my $time = shift @states; # field 0 is the time it takes
531            
532             # get the values from the current state
533 0         0 my @cur_states = @{$self->{"states_$self->{state}"}};
  0         0  
534 0         0 shift @cur_states; # dont need field 0
535              
536             # factor: endtime - time = starttime # 200 - 100 = 100
537             # tick - starttime = elapsedtime # 180 - 100 = 80
538             # time / elapsedtime = factor # 100 / 80 = 0.8 (80%)
539              
540 0         0 my $factor = $time / ($tick - ($self->{state_endtime} - $time));
541              
542             # interpolate linaer to the target values
543 0         0 while (@states > 0)
544             {
545             # 20 .. 80 => 60 * 0.8 (factor, 80%) = 48 + 20 => 68 as current value
546 0         0 $self->{$states[0]} =
547             ($states[1] - $cur_states[1]) * $factor + $cur_states[1];
548              
549 0         0 splice @states,0,2; # throw away first two entries
550 0         0 splice @cur_states,0,2; # throw away first two entries
551             }
552 0         0 1; # more changes to do
553             }
554              
555             ##############################################################################
556             # field access
557              
558             sub is
559             {
560 0     0 1 0 my ($self,$flag) = @_;
561              
562 0 0       0 if (!exists $self->{$flag})
563             {
564 0         0 require Carp;
565 0         0 Carp::croak ("Flag '$flag' does not exist at $self");
566             }
567 0         0 $self->{$flag};
568             }
569              
570             sub make
571             {
572 0     0 1 0 my ($self,$flag) = @_;
573              
574 0 0       0 if (!exists $self->{$flag})
575             {
576 0         0 require Carp;
577 0         0 Carp::croak ("Flag '$flag' does not exist at $self");
578             }
579 0         0 $self->{$flag} = 1;
580             }
581              
582             sub get
583             {
584 7     7 1 3338 my ($self,$field) = @_;
585              
586 7 50       22 if (!exists $self->{$field})
587             {
588 0         0 require Carp;
589 0         0 Carp::croak ("Field '$field' does not exist at " . $self->name());
590             }
591 7         43 $self->{$field};
592             }
593              
594             1;
595              
596             __END__