File Coverage

blib/lib/Games/Object.pm
Criterion Covered Total %
statement 634 730 86.8
branch 361 510 70.7
condition 137 198 69.1
subroutine 67 80 83.7
pod 12 31 38.7
total 1211 1549 78.1


line stmt bran cond sub pod time code
1             package Games::Object;
2              
3 10     10   30371 use strict;
  10         24  
  10         353  
4 10     10   52 use Exporter;
  10         17  
  10         374  
5              
6 10     10   54 use Carp qw(carp croak confess);
  10         19  
  10         757  
7 10     10   9834 use POSIX;
  10         78568  
  10         77  
8 10     10   41852 use IO::File;
  10         98178  
  10         3036  
9 10     10   10234 use IO::String 1.02;
  10         31630  
  10         354  
10 10     10   5526 use Games::Object::Common qw(ANAME_MANAGER FetchParams LoadData SaveData);
  10         38  
  10         1232  
11              
12 10     10   71 use vars qw($VERSION @EXPORT_OK %EXPORT_TAGS @ISA);
  10         31  
  10         1607  
13              
14             $VERSION = "0.11";
15             @ISA = qw(Exporter);
16             @EXPORT_OK = qw(ProcessList
17             OBJ_CHANGED OBJ_AUTOALLOCATED OBJ_PLACEHOLDER OBJ_DESTROYED
18             ATTR_STATIC ATTR_DONTSAVE ATTR_AUTOCREATE ATTR_NO_INHERIT
19             ATTR_NO_ACCESSOR
20             FLAG_NO_INHERIT
21             ACT_MISSING_OK
22             $CompareFunction $AccessorMethod $ActionMethod);
23             %EXPORT_TAGS = (
24             functions => [qw(ProcessList)],
25             objflags => [qw(OBJ_CHANGED OBJ_AUTOALLOCATED
26             OBJ_PLACEHOLDER OBJ_DESTROYED)],
27             attrflags => [qw(ATTR_STATIC ATTR_DONTSAVE ATTR_AUTOCREATE
28             ATTR_NO_INHERIT ATTR_NO_ACCESSOR)],
29             flagflags => [qw(FLAG_NO_INHERIT)],
30             actionflags => [qw(ACT_MISSING_OK)],
31             variables => [qw($CompareFunction $AccessorMethod $ActionMethod)],
32             );
33              
34 10     10   51 use vars qw($CompareFunction $AccessorMethod $ActionMethod);
  10         17  
  10         669  
35              
36             # Overload operations to allow simple comparisons to be performed easily.
37             #
38             # ALL operations can be overridden with no effect to this class. These operators
39             # are not used internally.
40             use overload
41 10         75 '<=>' => '_compare_pri',
42             'cmp' => '_compare_ids',
43             'bool' => '_do_nothing',
44 10     10   18548 '""' => 'id';
  10         21401  
45              
46             # Define some attribute flags.
47 10     10   7088 use constant ATTR_STATIC => 0x00000001;
  10         20  
  10         1004  
48 10     10   52 use constant ATTR_DONTSAVE => 0x00000002;
  10         25  
  10         456  
49 10     10   102 use constant ATTR_AUTOCREATE => 0x00000004;
  10         20  
  10         510  
50 10     10   46 use constant ATTR_NO_INHERIT => 0x00000008;
  10         20  
  10         413  
51 10     10   50 use constant ATTR_NO_ACCESSOR => 0x00000010;
  10         19  
  10         471  
52              
53             # Define some flag flags (i.e. internal flags on user-defined flag structures)
54 10     10   51 use constant FLAG_NO_INHERIT => 0x00000008;
  10         20  
  10         2377  
55              
56             # Define object flags (internal)
57 10     10   1393 use constant OBJ_CHANGED => 0x00000001;
  10         16  
  10         431  
58 10     10   47 use constant OBJ_AUTOALLOCATED => 0x00000002;
  10         15  
  10         433  
59 10     10   48 use constant OBJ_PLACEHOLDER => 0x00000004;
  10         17  
  10         408  
60 10     10   640 use constant OBJ_DESTROYED => 0x00000008;
  10         23  
  10         453  
61              
62             # Define action flags. Make sure these do not overlap with other flags
63             # so they can be used in combination with them.
64 10     10   48 use constant ACT_MISSING_OK => 0x00001000;
  10         19  
  10         2017  
65              
66             # Define default global options
67             $AccessorMethod = 0;
68             $ActionMethod = 0;
69              
70             # Define the comparison function to use for processing order.
71             $CompareFunction = '_CompareDefault';
72              
73             # Track the highest priority object so that we can insure the global object
74             # is higher.
75             my $highest_pri = 0;
76              
77             # Define a table that shows what order process() is supposed to do things
78             # by default.
79             my @process_list = (
80             'process_queue',
81             'process_pmod',
82             'process_tend_to',
83             );
84              
85             # Define a limit to how many times the same item can be processed in a queue
86             # (see process_queue() for details)
87             my $process_limit = 100;
88              
89             ####
90             ## INTERNAL FUNCTIONS
91              
92             # Round function provided for the -on_fractional option
93              
94 0     0 0 0 sub round { int($_[0] + 0.5); }
95              
96             # Check to see if a variable holds a reference to a Games::Object object
97              
98             sub _IsObject
99             {
100 65     65   80 my $obj = shift;
101 65 100       354 ref($obj) && UNIVERSAL::isa($obj, 'Games::Object');
102             }
103              
104             # Create an accessor method
105              
106             sub _CreateAccessorMethod
107             {
108 55     55   93 my ($name, $type) = @_;
109 10     10   54 no strict 'refs';
  10         20  
  10         8918  
110              
111 55 50       118 if ($type eq 'attr') {
    0          
112              
113             # Don't do anything if already defined.
114 55         75 my $simple = $name;
115 55         96 my $modify = "mod_$name";
116 55 100       277 return 1 if (defined(&$simple));
117              
118             # Create it.
119             *$simple = sub {
120 58     58   978 my $obj = shift;
121 58 50 66     219 @_ == 0 ? $obj->attr($name) :
    100 33        
    100          
    100          
122             @_ == 1 ? $obj->mod_attr(-name => $name, -value => $_[0]) :
123             @_ == 2 && _IsObject($_[1]) ?
124             $obj->mod_attr(-name => $name,
125             -value => $_[0],
126             -other => $_[1]) :
127             @_ == 3 && _IsObject($_[1]) && _IsObject($_[2]) ?
128             $obj->mod_attr(-name => $name,
129             -value => $_[0],
130             -other => $_[1],
131             -object => $_[2])
132             :
133             $obj->mod_attr(-name => $name, '-value', @_);
134 11         110 };
135             *$modify = sub {
136 16     16   302 my $obj = shift;
137 16 50 66     124 @_ == 1 ? $obj->mod_attr(-name => $name, -modify => $_[0]) :
    100 33        
    100          
138             @_ == 2 && _IsObject($_[1]) ?
139             $obj->mod_attr(-name => $name,
140             -modify => $_[0],
141             -other => $_[1]) :
142             @_ == 3 && _IsObject($_[1]) && _IsObject($_[2]) ?
143             $obj->mod_attr(-name => $name,
144             -modify => $_[0],
145             -other => $_[1],
146             -object => $_[2])
147             :
148             $obj->mod_attr(-name => $name, '-modify', @_);
149 11         108 };
150              
151             } elsif ($type eq 'flag') {
152              
153             # Don't do anything if already defined.
154 0 0       0 return 1 if (defined(&$name));
155              
156             # Create it.
157             *$name = sub {
158 0     0   0 my $obj = shift;
159 0         0 my ($val, $other) = @_;
160 0 0       0 $val ? $obj->set($name, $other) :
161             $obj->clear($name, $other);
162 0         0 };
163              
164             }
165              
166 11         25 1;
167             }
168              
169             # Create an action method.
170              
171             sub _CreateActionMethod
172             {
173 21     21   38 my $action = shift;
174 21         53 $action =~ /^on_(.+)$/;
175 21         49 my $verb = $1;
176              
177 10     10   1520 no strict 'refs';
  10         20  
  10         100960  
178              
179             # This form of the action method acts as a "verb". The first object is
180             # considered to be instigating the action and is thus other, self is
181             # is the object being acted upon, and object is an optional other
182             # item involved in the transaction. Examples:
183             #
184             # $player->use($camera);
185             # other = $player self = $camera
186             # Player snaps a picture
187             #
188             # $player->use($camera, $plant);
189             # other = $player self = $camera object = $plant
190             # Player snaps picture of plant
191             #
192             # $creature->give($player, $apple);
193             # other = $creature self = $player object = $apple
194             # Creature gives player the apple
195             *$verb = sub {
196 3     3   314 my $other = shift;
197 3 50       11 my $args = ( ref($_[$#_]) eq 'HASH' ? pop @_ : undef );
198 3 0       10 my ($self, $object) = (
    50          
    50          
199             @_ == 0 ? croak "Not enough arguments to $verb!" :
200             @_ == 1 ? ($_[0], undef ) :
201             @_ == 2 ? ( @_ ) :
202             croak "Too many arguments to $verb!" );
203 3         19 $self->action(action => "object:${action}",
204             other => $other,
205             object => $object,
206             args => $args);
207 21 100 100     216 } if (defined($verb) && !defined(&$verb));
208              
209             # The passive form is simply the original action triggered from self
210             # rather than other. Designed largely for peripheral actions or
211             # side-effect actions. For example, extending the "give" action above,
212             # you may want to call "on_given" on the $apple object.
213             #
214             # This is also used for actions that have neither other nor object
215             # parameters.
216             *$action = sub {
217 3     3   354 my $self = shift;
218 3 50       11 my $args = ( ref($_[$#_]) eq 'HASH' ? pop @_ : undef );
219 3 50       8 my $flags = ( !_IsObject($_[$#_]) ? pop @_ : 0 );
220 3 0       13 my ($other, $object) = (
    50          
    50          
221             @_ == 0 ? ( undef, undef ) :
222             @_ == 1 ? ( $_[0], undef ) :
223             @_ == 2 ? ( @_ ) :
224             croak "Too many arguments to $verb!" );
225 3         12 $self->action(action => "object:${action}",
226             other => $other,
227             object => $object,
228             flags => $flags,
229             args => $args);
230 21 100       199 } if (!defined(&$action));
231             }
232              
233             # Default comparison function when determining the order of processing of
234             # two objects.
235              
236 0     0   0 sub _CompareDefault { $b->{priority} <=> $a->{priority} }
237              
238             # Comparison function when using the creation order option
239              
240             sub _CompareCreationOrder {
241 0     0   0 my $cmp = $b->{priority} <=> $a->{priority};
242 0 0       0 $cmp == 0 ? $a->{order} <=> $b->{order} : $cmp;
243             }
244              
245             ####
246             ## FUNCTIONS
247              
248             # Fetch/set the process list for the process() function. Note that the user is
249             # not limited to the methods found here. The methods can be in the subclass
250             # if desired. Note that we have no way to validate the method names here,
251             # so we take it on good faith that they exist.
252              
253 0 0   0 0 0 sub ProcessList { if (@_) { @process_list = @_ } else { @process_list } }
  0         0  
  0         0  
254              
255             ####
256             ## INTERNAL METHODS
257              
258             # Do absolutely nothing successfully.
259              
260 1731     1731   4850 sub _do_nothing { 1; }
261              
262             # Do absolutely nothing, but fail at it.
263              
264 0     0   0 sub _do_nothing_fail { 0; }
265              
266             # Set an internal flag on object.
267              
268             sub _set
269             {
270 0     0   0 my ($obj, $flag) = @_;
271              
272 0         0 $obj->{_flags} |= $flag;
273             }
274              
275             # Clear an internal flag on object.
276              
277             sub _clear
278             {
279 0     0   0 my ($obj, $flag) = @_;
280              
281 0         0 $obj->{_flags} &= (0xffffffff ^ $flag);
282             }
283              
284             # Check if an internal flag is set.
285              
286             sub _is
287             {
288 0     0   0 my ($obj, $flag) = @_;
289              
290 0         0 ($obj->{_flags} & $flag) == $flag;
291             }
292              
293             # Wipe all values from object except for the ID and DONTSAVE attributes.
294              
295             sub _wipe
296             {
297 0     0   0 my $obj = shift;
298              
299 0         0 foreach my $key (keys %$obj) {
300 0 0       0 next if ($key eq 'id');
301 0 0       0 if ($key eq 'attr') {
302 0         0 foreach my $aname (keys %{$obj->{attr}}) {
  0         0  
303 0         0 my $attr = $obj->{attr}{$aname};
304 0 0       0 delete $obj->{attr}{$aname}
305             if ( !($attr->{flags} & ATTR_DONTSAVE) );
306             }
307             } else {
308 0         0 delete $obj->{$key};
309             }
310             }
311 0         0 $obj;
312             }
313              
314             # "Lock" a method call so that it cannot be called again, thus practioning
315             # recursion. If it is already locked, then this is a fatal error, indicating
316             # that recursion has occurred.
317              
318             sub _lock_method
319             {
320 123     123   183 my ($obj, $meth) = @_;
321 123         180 my $lock = "__" . $meth;
322              
323 123 50       289 if (defined($obj->{$lock})) {
324 0         0 croak("Attempt to call '$meth' on '$obj->{id}' recursively");
325             } else {
326 123         323 $obj->{$lock} = 1;
327             }
328             }
329              
330             # Unlock a method
331              
332             sub _unlock_method
333             {
334 123     123   179 my ($obj, $meth) = @_;
335 123         197 my $lock = "__" . $meth;
336              
337 123         364 delete $obj->{$lock};
338             }
339              
340             # Compare the IDs of two objects.
341              
342             sub _compare_ids
343             {
344 56     56   7035 my ($obj1, $obj2, $swapped) = @_;
345 56         123 my $id1 = $obj1->id();
346 56 100       180 my $id2 = ref($obj2) ? $obj2->id() : $obj2;
347              
348 56 100       405 $swapped ? $id2 cmp $id1 : $id1 cmp $id2;
349             }
350              
351             # Compare the priorities of two objects.
352              
353             sub _compare_pri
354             {
355 12     12   4214 my ($obj1, $obj2, $swapped) = @_;
356 12         57 my $pri1 = $obj1->priority();
357 12 100       25 my $pri2 = ref($obj2) ? $obj2->priority() : $obj2;
358              
359 12 100       86 $swapped ? $pri2 <=> $pri1 : $pri1 <=> $pri2;
360             }
361              
362             ####
363             ## CONSTRUCTOR
364              
365             # Basic constructor.
366              
367             sub new
368             {
369 63     63 0 2513 my $proto = shift;
370 63   33     327 my $class = ref($proto) || $proto;
371 63         113 my $obj = {};
372 63         124 my %args = ();
373              
374             # Fetch optional parameters.
375 63         620 FetchParams(\@_, \%args, [
376             [ 'opt', 'id', undef, 'string' ],
377             [ 'opt', '^on_', undef, 'callback' ],
378             [ 'opt', '^try_', undef, 'callback' ],
379             [ 'opt', 'class', undef, 'object' ],
380             [ 'opt', 'priority', 0, 'int' ],
381             ] );
382              
383             # Bless object and set user-provided values, if defined.
384 63         380 bless $obj, $class;
385 63 100       1290 $obj->{id} = $args{id} if (defined($args{id}));
386 63         437 $obj->{priority} = $args{priority};
387              
388             # Initialize internal data structures.
389 63         132 $obj->{_flags} = 0;
390 63         129 $obj->{attr} = {};
391 63         145 $obj->{flag} = {};
392 63         120 $obj->{queue} = [];
393 63         103 $obj->{priority} = 0;
394 63         115 $obj->{pmod} = {};
395 63         132 $obj->{pmod_next} = 0;
396 63         123 $obj->{pmod_active} = 0;
397              
398             # For each on_* action, create a matching attribute to store the
399             # actual callback data and delete the original parameter. This way
400             # we can use simple inheritance and don't have to write seperate code
401             # to handle it.
402 63         149 foreach my $action (grep { /^(on|try)_/ } keys %args) {
  115         362  
403 16         41 my $callbk = delete $args{$action};
404 16         84 $obj->del_attr($action);
405 16         109 $obj->new_attr(
406             -name => "_ACT_${action}",
407             -type => "any",
408             -value => $callbk,
409             -flags => ATTR_NO_ACCESSOR,
410             );
411 16 100       62 _CreateActionMethod($action) if ($ActionMethod);
412             }
413              
414             # Done.
415 63         267 $obj;
416             }
417              
418             # Load an object from an open file. You can call this in one of several ways:
419             #
420             # - As a class method, which generates a totally new object.
421             # - As an object method, which loads the object "in place" (i.e. overriting
422             # the current object, except for the ID, which is preserved if defined)
423             #
424             # You can also call this with a "file" arg (which is an open file), or
425             # "filename" (which is a filename that is opened and closed for you)
426              
427             sub load
428             {
429 19     19 1 1428 my $proto = shift;
430 19   33     74 my $class = ref($proto) || $proto;
431 19         31 my %args = ();
432              
433             # Check for occurrence of single parameter and turn into appropriate
434             # named parameter if found.
435 19 100 66     74 unshift @_, "file" if (@_ == 1 && ref($_[0]));
436 19 50 33     69 unshift @_, "filename" if (@_ == 1 && !ref($_[0]));
437              
438             # Fetch parameters.
439 19         247 FetchParams(\@_, \%args, [
440             [ 'opt', 'file', undef, 'file' ],
441             [ 'opt', 'filename', undef, 'string' ],
442             [ 'opt', 'id', undef, 'string' ],
443             [ 'opt', 'other', undef, 'object' ],
444             ]);
445              
446             # Check the file args.
447 19 50 66     120 croak "Cannot define both 'filename' and 'file' args to object " .
448             "constructor"
449             if (defined($args{file}) && defined($args{filename}));
450 19 100       66 if (defined($args{filename})) {
    50          
451 1         8 $args{file} = IO::File->new();
452 1 50       35 $args{file}->open("<$args{filename}")
453             or croak "Unable to open filename '$args{filename}' for read";
454             } elsif (!defined($args{file})) {
455 0         0 croak "One of 'file' or 'filename' must be specified to load()"
456             }
457              
458             # First check that the file really contains an object definition at
459             # this point. We need to do this anyway since we need the ID stored
460             # there. NOTE: The assignment to $file is necessary, as <$args{file}>
461             # will not parse.
462 19         73 my $file = $args{file};
463 19         94 my $line = <$file>;
464 19 50       40 croak("Attempt to read object data past EOF") if (!defined($line));
465 19 50       67 croak("File does not contain object data at present position")
466             if ($line !~ /^OBJ:(.+)$/);
467 19         35 my $id = $1;
468              
469             # Now fetch the saved class of the object, so we can re-bless it into
470             # the user's subclass.
471 19         85 $line = <$file>;
472 19 50       39 croak("Attempt to read object data past EOF") if (!defined($line));
473 19 50       71 croak("File does not contain class data at present position")
474             if ($line !~ /^CL:(.+)$/);
475 19         32 my $subclass = $1;
476              
477             # How were we called?
478 19         19 my $obj;
479 19 50       40 if (_IsObject($proto)) {
480             # As an object method, so we do a "load in place". Clear out
481             # everything except the ID, if defined.
482 0         0 $obj->_wipe();
483             } else {
484             # Create a totally new object from this.
485 19         55 $obj = Games::Object->new();
486             }
487              
488             # If the user overrides the ID, or the ID exists in the object already,
489             # then we set that here.
490 19 100       61 if (defined($args{id})) { $id = $args{id}; }
  9 50       12  
491 0         0 elsif (defined($obj->{id})) { $id = $obj->{id}; }
492              
493             # We now have an object ready to load into, so perform the load.
494 19         50 $obj->_protect_attrs(\&LoadData, $file, $obj);
495              
496             # Close the file if we opened it.
497 19 100       88 $file->close() if (defined($args{filename}));
498              
499             # Look for snapshots of attributes that had been created with the
500             # AUTOCREATE option and instantiate these, but ONLY if they do not
501             # already exist (thus a load-in-place will not clobber them)
502 19         40 foreach my $aname (keys %{$obj->{snapshots}}) {
  19         78  
503 2 50       8 if (!defined($obj->{attr}{$aname})) {
504 2         3 my $attr = {};
505 2         6 my $snapshot = $obj->{snapshots}{$aname};
506 2         7 foreach my $key (keys %$snapshot) {
507 10 50       51 $attr->{$key} = (
    50          
    100          
508             $key =~ /^(value|real_value)$/ ? (
509             ref($snapshot->{$key}) eq 'ARRAY' ? [ ] :
510             ref($snapshot->{$key}) eq 'HASH' ? { } :
511             $snapshot->{$key}
512             ) :
513             $snapshot->{$key}
514             );
515             }
516 2         7 $obj->{attr}{$aname} = $attr;
517             }
518             }
519              
520             # (Re)create accessors if user wants it.
521 19 100       46 if ($AccessorMethod) {
522 8         11 foreach my $aname (keys %{$obj->{attr}}) {
  8         29  
523 23 100       87 _CreateAccessorMethod($aname, 'attr')
524             unless ($obj->{attr}{$aname}{flags} & ATTR_NO_ACCESSOR);
525             }
526             }
527 19 100       45 if ($ActionMethod) {
528 8         11 foreach my $aname (grep { /^_ACT_/ } keys %{$obj->{attr}}) {
  23         59  
  8         24  
529 7         21 $aname =~ /^_ACT_(.+)$/;
530 7         13 my $action = $1;
531 7         15 _CreateActionMethod($action);
532             }
533             }
534              
535             # Make sure the ID is what we expect.
536 19         51 $obj->{id} = $id;
537              
538             # Done. Rebless into this subclass and invoke any action binding
539             # on the object:load action.
540 19 100       49 bless $obj, $subclass if ($subclass ne 'Games::Object');
541 19         91 $obj->action(
542             other => $args{other},
543             action => 'object:on_load',
544             args => { file => $file },
545             );
546 19         137 $obj;
547             }
548              
549             ####
550             ## OBJECT DATA METHODS
551              
552             # Save an object to a file at the present position. At the moment, everything
553             # is saved in clear ASCII. This makes the file portable across architectures
554             # while sacrificing space and security. Later versions of this module will
555             # include other formats.
556              
557             sub save
558             {
559 18     18 1 649 my ($obj) = shift;
560 18         28 my %args = ();
561              
562             # Check for occurrence of single parameter and turn into appropriate
563             # named parameter if found.
564 18 100 66     74 unshift @_, "file" if (@_ == 1 && ref($_[0]));
565 18 50 33     46 unshift @_, "filename" if (@_ == 1 && !ref($_[0]));
566              
567             # Fetch parameters
568 18         116 FetchParams(\@_, \%args, [
569             [ 'opt', 'file', undef, 'file' ],
570             [ 'opt', 'filename', undef, 'string' ],
571             [ 'opt', 'other', undef, 'object' ],
572             ]);
573              
574             # Check the file args.
575 18 50 33     147 croak "Cannot define both 'filename' and 'file' args to save()"
576             if (defined($args{file}) && defined($args{filename}));
577 18 50       73 if (defined($args{filename})) {
    50          
578 0         0 $args{file} = IO::File->new();
579 0 0       0 $args{file}->open(">$args{filename}")
580             or croak "Unable to open filename '$args{filename}' for write";
581             } elsif (!defined($args{file})) {
582 0         0 croak "One of 'file' or 'filename' must be specified to save()"
583             }
584 18         27 my $file = $args{file};
585              
586             # Save the ID
587 18         76 print $file "OBJ:$obj->{id}\n";
588              
589             # Save the object class.
590 18         36 print $file "CL:" . ref($obj) . "\n";
591              
592             # Now all we need to do is call SaveData() on ourself. However, if
593             # we use $obj directly, SaveData will simply call save() all over
594             # again and we have ourselves an infinite loop, which is bad. We need to
595             # fool it into thinking its a hash. So we assign %$obj to an ordinary
596             # hash and pass the ref to it. This forces the reference to lose its
597             # magic. Even better, no duplicate of the hash is made. %hash internally
598             # contains the same reference, but without the blessing magic on it.
599             #
600             # Note that we do not want to save DONTSAVE attributes, so we run it
601             # through the special wrapper.
602 18         135 my %hash = %$obj;
603 18         87 $obj->_protect_attrs(\&SaveData, $file, \%hash);
604              
605             # Close the file if we opened it.
606 18 50       56 $file->close() if ($args{filename});
607              
608             # Invoke any action bindings.
609 18         93 $obj->action(
610             other => $args{other},
611             action => 'object:on_save',
612             args => { file => $file },
613             );
614              
615             }
616              
617             # This is an interface to the object's manager's find() method. This is
618             # essentially shorthand for "do a find() for an ID in the manager of this
619             # other object". Note that we do not treat the lack of a manager as an error,
620             # but simply report the same as not finding the object.
621              
622             sub find
623             {
624 42     42 1 86 my ($obj, $id) = @_;
625 42         87 my $man = $obj->manager();
626              
627 42 50       182 $man ? $man->find($id) : undef;
628             }
629              
630             # Ditto to the manager's order() method
631              
632             sub order
633             {
634 0     0 1 0 my $obj = shift;
635 0         0 my $man = $obj->manager();
636              
637 0 0       0 $man ? $man->order($obj) : undef;
638             }
639              
640             ###
641             ## FLAG METHODS
642              
643             # Create a flag on an object.
644              
645             sub new_flag
646             {
647 5     5 0 998 my $obj = shift;
648 5         9 my $flag = {};
649              
650             # Fetch parameters
651 5         44 FetchParams(\@_, $flag, [
652             [ 'req', 'name', undef, 'string' ],
653             [ 'opt', 'value', 0, 'boolean' ],
654             [ 'opt', 'flags', 0, 'int' ],
655             [ 'opt', 'on_set', undef, 'callback' ],
656             [ 'opt', 'on_clear', undef, 'callback' ],
657             ] );
658              
659             # Set on object and done.
660 5         19 my $fname = delete $flag->{name};
661 5         14 $obj->{flag}{$fname} = $flag;
662 5         33 1;
663             }
664              
665             # Set flag on object.
666              
667             sub set
668             {
669 6     6 0 541 my ($obj, $fname, $other) = @_;
670              
671             # Check for multiple flags.
672 6 100       17 if (ref($fname) eq 'ARRAY') {
673             # Call myself multiple times.
674 1         3 foreach (@$fname) { $obj->set($_, $other); }
  2         6  
675 1         8 return $obj;
676             }
677              
678             # Find the flag.
679 5         10 my ($flag, $inherited) = $obj->_find_flag($fname);
680 5 50       11 croak("Attempt to set undefined user flag '$fname' on '$obj->{id}'")
681             unless (defined($flag));
682              
683             # If we inherited this flag, then clone it so that we have
684             # our own copy. We do this via a clever trick: Using IO::String
685             # to create a stringified version of the data.
686 5 50       9 if ($inherited) {
687 0         0 $obj->{flag}{$fname} = {};
688 0         0 my $iostr = IO::String->new();
689 0         0 SaveData($iostr, $flag);
690 0         0 seek $iostr, 0, 0;
691 0         0 LoadData($iostr, $obj->{flag}{$fname});
692 0         0 $flag = $obj->{flag}{$fname};
693             }
694              
695             # Do it.
696 5 50       11 if ($flag->{value} != 1) {
697 5         6 $flag->{value} = 1;
698 5         25 $obj->action(
699             other => $other,
700             action => "flag:${fname}:on_set",
701             args => { name => $fname },
702             );
703             }
704 5         26 $obj;
705             }
706              
707             # Clear flag on object.
708              
709             sub clear
710             {
711 4     4 0 23 my ($obj, $fname, $other) = @_;
712              
713 4 50       14 if (ref($fname) eq 'ARRAY') {
714             # Call myself multiple times.
715 0         0 foreach (@$fname) { $obj->clear($_, $other); }
  0         0  
716 0         0 return $obj;
717             }
718              
719             # Find flag.
720 4         13 my ($flag, $inherited) = $obj->_find_flag($fname);
721 4 50       13 croak("Attempt to clear undefined user flag '$fname' on '$obj->{id}'")
722             unless (defined($flag));
723              
724             # If we inherited this flag, then clone it so that we have
725             # our own copy. We do this via a clever trick: Using IO::String
726             # to create a stringified version of the data.
727 4 50       12 if ($inherited) {
728 0         0 $obj->{flag}{$fname} = {};
729 0         0 my $iostr = IO::String->new();
730 0         0 SaveData($iostr, $flag);
731 0         0 seek $iostr, 0, 0;
732 0         0 LoadData($iostr, $obj->{flag}{$fname});
733 0         0 $flag = $obj->{flag}{$fname};
734             }
735              
736             # Do it.
737 4 100       41 if ($flag->{value} != 0) {
738 3         7 $flag->{value} = 0;
739 3         19 $obj->action(
740             other => $other,
741             action => "flag:${fname}:on_clear",
742             args => { name => $fname },
743             );
744             }
745 4         26 $obj;
746             }
747              
748             # Check to see if one or more flags are set on an object (all must be set
749             # to be true).
750              
751             sub is
752             {
753 18     18 1 915 my ($obj, @fnames) = @_;
754 18         25 my $total = 0;
755              
756 18         31 foreach my $fname (@fnames) {
757 23         47 my $flag = $obj->_find_flag($fname);
758 23 100 100     121 $total++ if (defined($flag) && $flag->{value});
759             }
760 18         80 $total == scalar(@fnames);
761             }
762              
763             # Same as above, but returns true so long as at least one flag is present.
764              
765             sub maybe
766             {
767 2     2 0 6 my ($obj, @fnames) = @_;
768 2         4 my $total = 0;
769              
770 2         3 foreach my $fname (@fnames) {
771 4         9 my $flag = $obj->_find_flag($fname);
772 4 50       8 croak("User flag '$fname' on '$obj->{id}' is undefined in maybe()")
773             unless (defined($flag));
774 4 100       10 $total++ if ($flag->{value});
775 4 100       10 last if $total;
776             }
777 2         9 $total;
778             }
779              
780             ####
781             ## INTERNAL ATTRIBUTE METHODS
782              
783             # Adjust integer attribute to get rid of fractionals.
784              
785             sub _adjust_int_attr
786             {
787 100     100   173 my ($obj, $aname) = @_;
788 100         340 my $attr = $obj->{attr}{$aname};
789              
790 100         241 my $expr1 = '$attr->{value} = ' .
791             $attr->{on_fractional} .
792             '($attr->{value})';
793 100         190 my $expr2 = '$attr->{real_value} = ' .
794             $attr->{on_fractional} .
795             '($attr->{real_value})';
796 100         7085 eval($expr1);
797 100 100       1519 eval($expr2) if (defined($attr->{real_value}));
798             }
799              
800             # Set an attribute to a new value, taking into account limitations on the
801             # attribute's value, plus adjustments for fractionals and so on.
802              
803             sub _set_attr
804             {
805 109     109   392 my ($obj, $aname, %args) = @_;
806 109         223 my $attr = $obj->{attr}{$aname};
807              
808 109         179 foreach my $key (qw(real_value value)) {
809              
810             # Fetch old and new values.
811 218 100       552 next if (!defined($args{$key}));
812 109         156 my $old = $attr->{$key};
813 109         139 my $new = $args{$key};
814              
815             # If this is a non-numeric data type, then set it, call action
816             # if needed, and done.
817 109 100       464 if ($attr->{type} !~ /^(int|number)$/) {
818 1 50       5 croak "Non-numeric attributes cannot have split values"
819             if ($key eq 'real_value');
820 1 50       4 if ($attr->{type} eq 'object') {
821             # This must be an object reference, but NOT a
822             # Games::Object-derived object.
823 1 50       3 croak "Value to store in 'object' attribute must be " .
824             "a real object reference, not a simple scalar"
825             if (!ref($new));
826 1 50       3 croak "Value to store in 'object' attribute must be " .
827             "a real object reference not a " . ref($new) .
828             "reference"
829             if (ref($new) =~ /SCALAR|ARRAY|HASH|CODE|LVALUE|GLOB/);
830 1 50       4 croak "Cannot store a Games::Object-derived object in ".
831             "an 'object' attribute (use object relationships " .
832             "in the manager for that)" if (_IsObject($new));
833             }
834 1         2 $attr->{$key} = $new;
835 1 50 33     22 $obj->action(
      33        
836             other => $args{other},
837             object => $args{object},
838             flags => $attr->{flags},
839             action => "attr:${aname}:on_change",
840             args => {
841             name => $aname,
842             old => $old,
843             new => $new,
844             },
845             ) if (!$args{no_action} && $old ne $new && $key eq 'value');
846 1         5 next;
847             }
848              
849             # Find out if the new value is out of bounds. Note that for the
850             # purposes of this code, we consider being right on the bounds
851             # as OOB (perhaps this should be called OOOAB - Out Of Or At Bounds)
852 108   100     405 my $too_small = ( defined($attr->{minimum}) &&
853             $new <= $attr->{minimum} );
854 108   100     322 my $too_big = ( defined($attr->{maximum}) &&
855             $new >= $attr->{maximum} );
856 108   100     357 my $oob = ( $too_small || $too_big );
857 108         110 my $excess;
858 108 100       219 if ($oob) {
859              
860             # Yes. Do we force it?
861 14 50       35 if (!$args{force}) {
862              
863             # No, don't force it. But what do we do with the
864             # modification?
865 14         22 my $oob_what = $attr->{out_of_bounds};
866 14 100       30 if ($oob_what eq 'ignore') {
867              
868             # Ignore this change.
869 3         10 next;
870              
871             } else {
872              
873             # Either use up what we can up to limit, or track the
874             # excess. In either case, we need to calculate the
875             # amount of excess. Note that 'track' is kind of like
876             # an implied force option.
877 11 100       27 if ($too_small) {
878 4         10 $excess = $attr->{minimum} - $new;
879 4 50       16 $new = $attr->{minimum} if ($oob_what eq 'use_up');
880             } else {
881 7         15 $excess = $new - $attr->{maximum};
882 7 50       28 $new = $attr->{maximum} if ($oob_what eq 'use_up');
883             }
884              
885             }
886              
887             } # if !$args{force}
888              
889             } # if $oob;
890              
891             # Set the new value.
892 105         166 $attr->{$key} = $new;
893              
894             # Adjust it if fractional and we're not handling those.
895 105 100 100     551 $obj->_adjust_int_attr($aname)
896             if ($attr->{type} eq 'int' && !$attr->{track_fractional});
897 105         179 $new = $attr->{$key};
898              
899             # Invoke modified action, but ONLY if it was modified.
900 105 100 66     1561 $obj->action(
      100        
901             other => $args{other},
902             object => $args{object},
903             flags => $attr->{flags},
904             action => "attr:${aname}:on_change",
905             args => {
906             name => $aname,
907             old => $old,
908             new => $new,
909             change => ( $new - $old ),
910             },
911             ) if (!$args{no_action} && $old != $new && $key eq 'value');
912              
913             # Invoke OOB actions
914 104 50 66     1111 $obj->action(
      66        
      66        
915             other => $args{other},
916             object => $args{object},
917             flags => $attr->{flags},
918             action => "attr:${aname}:on_minimum",
919             args => {
920             name => $aname,
921             old => $old,
922             new => $new,
923             excess => $excess,
924             change => ( $new - $old ),
925             },
926             ) if (!$args{no_action} && $too_small && $old != $new
927             && $key eq 'value');
928 104 100 66     703 $obj->action(
      100        
      66        
929             other => $args{other},
930             object => $args{object},
931             flags => $attr->{flags},
932             action => "attr:${aname}:on_maximum",
933             args => {
934             name => $aname,
935             old => $old,
936             new => $new,
937             excess => $excess,
938             change => ( $new - $old ),
939             },
940             ) if (!$args{no_action} && $too_big && $old != $new
941             && $key eq 'value');
942              
943             } # foreach $key
944              
945             # Done.
946 108         342 1;
947             }
948              
949             # Run code with a wrapper designed to protect the DONTSAVE attributes.
950              
951             sub _protect_attrs
952             {
953 37     37   76 my ($obj, $code, @args) = @_;
954              
955             # Save off the DONTSAVE attributes and delete from object.
956 37         49 my %temp = ();
957 37         45 foreach my $aname (keys %{$obj->{attr}}) {
  37         120  
958 68         102 my $attr = $obj->{attr}{$aname};
959 68 100       180 if ($attr->{flags} & ATTR_DONTSAVE) {
960 10         17 $temp{$aname} = $attr;
961 10         34 delete $obj->{attr}{$aname};
962             }
963             }
964              
965             # Run the indicated code.
966 37         129 &$code(@args);
967              
968             # Put back the attributes that we temporarily nixed.
969 37         105 foreach my $aname (keys %temp) {
970 10         43 $obj->{attr}{$aname} = $temp{$aname};
971             }
972             }
973              
974             # Find an attribute. This performs inheritance logic to find a viable attribute
975             # no matter where it resides.
976             #
977             # In a scalar context, it simply returns the hash ref of the attribute. In
978             # an array context, it returns a list consisting of the hash ref and a flag
979             # indicating whether this was inherited or not.
980             #
981             # Note that inheritance requires that the object manager be set up with
982             # the inherit relationship or it only looks on the current object.
983              
984             sub _find_attr
985             {
986 1169     1169   1479 my ($obj, $aname) = @_;
987 1169         1207 my $attr;
988 1169         1366 my $inherited = 0;
989              
990             # Fetch the manager of this object, unless we're accessing the manager
991             # attribute itself, in which case we act as if there is no manager.
992             # This is to prevent infinite loops with manager(). Anyway, this
993             # attribute is not allowed to be inherited, so it works out.
994 1169 100       2642 my $man = ( $aname eq ANAME_MANAGER ? undef : $obj->manager() );
995              
996             # Check for no inheritance relation
997 1169 100 66     3648 if (!$man || !$man->has_relation('inherit')) {
998              
999 750 100       1856 if (defined($obj->{attr}{$aname})) {
1000 564 100       1939 wantarray ? ( $obj->{attr}{$aname}, 0 ) : $obj->{attr}{$aname};
1001             } else {
1002 186 50       461 wantarray ? ( undef, 0 ) : undef;
1003             }
1004              
1005             } else {
1006              
1007             # Do it
1008 419         495 my $aobj = $obj;
1009 419   100     2207 while (!$attr && $aobj) {
1010 444 100       1357 if (defined($aobj->{attr}{$aname})) {
    100          
1011             # Found attribute.
1012 347         621 $attr = $aobj->{attr}{$aname};
1013 347         671 $inherited = ( $aobj->{id} ne $obj->{id} );
1014 347 100 100     1396 if ($inherited && $attr->{flags} & ATTR_NO_INHERIT) {
1015             # But it was found on a inherit, and we're not allowed
1016             # to inherit this attribute, so this is as good as not
1017             # being defined at all. Note that we leave $inherited
1018             # set, so the caller can tell if we failed to find it
1019             # because it did not exist or could not be inherited, in
1020             # case that makes a difference to the caller.
1021 3         5 undef $attr;
1022 3         6 last;
1023             }
1024             } elsif ($man->inheriting_from($aobj)) {
1025             # We have an inheritance, so check it.
1026 25         70 $aobj = $man->inheriting_from($aobj);
1027             } else {
1028             # No more inheritance up the line, so we stop.
1029 72         371 undef $aobj;
1030             }
1031             }
1032              
1033             # Return the result
1034 419 100       1188 wantarray ? ( $attr, $inherited ) : $attr;
1035             }
1036             }
1037              
1038             # Do the exact same thing for object flags. See _find_attr() for explanation
1039             # of the logic.
1040              
1041             sub _find_flag
1042             {
1043 44     44   68 my ($obj, $fname) = @_;
1044 44         46 my $flag;
1045 44         44 my $inherited = 0;
1046              
1047             # Fetch the manager of this object.
1048 44         78 my $man = $obj->manager();
1049              
1050             # Check for no inheritance relation
1051 44 100 66     127 if (!$man || !$man->has_relation('inherit')) {
1052              
1053 39 50       78 if (defined($obj->{flag}{$fname})) {
1054 39 100       108 wantarray ? ( $obj->{flag}{$fname}, 0 ) : $obj->{flag}{$fname};
1055             } else {
1056 0 0       0 wantarray ? ( undef, 0 ) : undef;
1057             }
1058              
1059             } else {
1060              
1061             # Do it
1062 5         8 my $fobj = $obj;
1063 5   100     21 while (!$flag && $fobj) {
1064 8 100       34 if (defined($fobj->{flag}{$fname})) {
    100          
1065             # Found flag.
1066 4         9 $flag = $fobj->{flag}{$fname};
1067 4         10 $inherited = ( $fobj->{id} ne $obj->{id} );
1068 4 50 66     25 if ($inherited && $flag->{flags} & FLAG_NO_INHERIT) {
1069             # But it was found on a inherit, and we're not allowed
1070             # to inherit this attribute, so this is as good as not
1071             # being defined at all. Note that we leave $inherited
1072             # set, so the caller can tell if we failed to find it
1073             # because it did not exist or could not be inherited, in
1074             # case that makes a difference to the caller.
1075 0         0 undef $flag;
1076 0         0 last;
1077             }
1078             } elsif ($man->inheriting_from($fobj)) {
1079             # We have an inheritance, so check it.
1080 3         11 $fobj = $man->inheriting_from($fobj);
1081             } else {
1082             # No more inheritance up the line, so we stop.
1083 1         6 undef $fobj;
1084             }
1085             }
1086              
1087             # Return the result
1088 5 100       18 wantarray ? ( $flag, $inherited ) : $flag;
1089             }
1090              
1091             }
1092              
1093             ####
1094             ## ATTRIBUTE METHODS
1095              
1096             # Create a new attribute on an object.
1097             #
1098             # Attribute flags:
1099             # ATTR_STATIC - Attribute is not to be altered. Attempts to do so
1100             # are treated as an error.
1101             # ATTR_DONTSAVE - Don't save attribute on a call to save(). Also,
1102             # the existing value is preserved on a load().
1103             # ATTR_NO_INHERIT - Do not allow this attribute to be inherited.
1104              
1105             sub new_attr
1106             {
1107 140     140 0 3055 my $obj = shift;
1108 140         224 my $attr = {};
1109              
1110             # Fetch params universal to all attribute types.
1111 140         1309 FetchParams(\@_, $attr, [
1112             [ 'req', 'name' ],
1113             [ 'opt', 'type', 'any', [ qw(any int number string object) ] ],
1114             [ 'opt', 'priority', 0, 'int' ],
1115             [ 'opt', 'flags', 0, 'int' ],
1116             [ 'opt', 'on_change', undef, 'callback' ],
1117             ], 1 );
1118              
1119             # Fetch additional args for integer types. Note that we allow the
1120             # initial value to be fractional. We'll clean this up shortly.
1121 140 100       1166 FetchParams(\@_, $attr, [
1122             [ 'req', 'value', undef, 'number' ],
1123             [ 'opt', 'real_value', undef, 'number' ],
1124             [ 'opt', 'on_fractional', 'int', [ qw(int ceil floor round) ] ],
1125             [ 'opt', 'track_fractional', '0', 'boolean' ],
1126             [ 'opt', 'tend_to_rate', undef, 'number' ],
1127             [ 'opt', 'minimum', undef, 'int' ],
1128             [ 'opt', 'maximum', undef, 'int' ],
1129             [ 'opt', 'on_minimum', undef, 'callback' ],
1130             [ 'opt', 'on_maximum', undef, 'callback' ],
1131             [ 'opt', 'out_of_bounds', 'use_up', [ qw(use_up ignore track) ] ],
1132             ], 1 ) if ($attr->{type} eq 'int');
1133              
1134             # Fetch additional args for number types.
1135 140 100       649 FetchParams(\@_, $attr, [
1136             [ 'req', 'value', undef, 'number' ],
1137             [ 'opt', 'real_value', undef, 'number' ],
1138             [ 'opt', 'tend_to_rate', undef, 'number' ],
1139             [ 'opt', 'minimum', undef, 'number' ],
1140             [ 'opt', 'maximum', undef, 'number' ],
1141             [ 'opt', 'on_minimum', undef, 'callback' ],
1142             [ 'opt', 'on_maximum', undef, 'callback' ],
1143             [ 'opt', 'out_of_bounds', 'use_up', [ qw(use_up ignore track) ] ],
1144             [ 'opt', 'precision', 2, 'int' ],
1145             ], 1 ) if ($attr->{type} eq 'number');
1146              
1147             # Fetch additional args for string types.
1148 140 100       510 FetchParams(\@_, $attr, [
1149             [ 'opt', 'values', undef, 'arrayref' ],
1150             [ 'opt', 'value', undef, 'string' ],
1151             [ 'opt', 'map', {}, 'hashref' ],
1152             ], 1 ) if ($attr->{type} eq 'string');
1153              
1154             # Fetch additional args for object types. Object refs are stored as-is,
1155             # and it is assumed they will have their own custom load/save methods.
1156             # Storing Games::Object-derived objects is prohibited; use the
1157             # manager's object relationship features for that.
1158 140 100       342 if ($attr->{type} eq 'object') {
1159 2         15 FetchParams(\@_, $attr, [
1160             [ 'opt', 'value', undef, 'object' ],
1161             ], 1 );
1162 2 50 33     16 croak "Cannot use type 'object' for Games::Object-derived " .
1163             "objects (use object relationships in the manager for that)"
1164             if (defined($attr->{value}) && _IsObject($attr->{value}));
1165             }
1166              
1167             # Fetch additional args for 'any' type.
1168 140 100       534 FetchParams(\@_, $attr, [
1169             [ 'opt', 'value', undef, 'any' ],
1170             ], 1 ) if ($attr->{type} eq 'any');
1171              
1172             # If there are any remaining arguments, sound a warning. Most likely
1173             # the caller forgot to put a 'type' parameter in.
1174 140 50       405 if (@_) {
1175 0         0 my %args = @_;
1176 0         0 my $extra = "'" . join("', '", keys %args) . "'";
1177 0         0 carp("Warning: extra args $extra to new_attr($attr->{name}) " .
1178             "of '$obj->{id}' ignored (did you forget a 'type' " .
1179             "parameter?)");
1180             }
1181              
1182             # Store.
1183 140         294 my $aname = delete $attr->{name};
1184 140         530 $obj->{attr}{$aname} = $attr;
1185 140 100 100     659 _CreateAccessorMethod($aname, 'attr')
1186             if ($AccessorMethod && !($attr->{flags} & ATTR_NO_ACCESSOR));
1187              
1188             # If a real_value was defined but no tend-to, drop the real_value.
1189 140 100       409 delete $attr->{real_value} if (!defined($attr->{tend_to_rate}));
1190              
1191             # And if there is a tend_to_rate but no real_value, set the latter
1192             # to the current value.
1193 140 50 66     427 $attr->{real_value} = $attr->{value}
1194             if (defined($attr->{tend_to_rate}) && !defined($attr->{real_value}));
1195              
1196             # Adjust attribute values to get rid of fractionals if not tracking it.
1197 140 100 100     596 $obj->_adjust_int_attr($aname)
1198             if ($attr->{type} eq 'int' && !$attr->{track_fractional});
1199              
1200             # Finally, if DONTSAVE and AUTOCREATE were used together, then
1201             # take a kind of "snapshot" of this attribute so it can be later
1202             # restored.
1203 140 100 100     505 if ( ($attr->{flags} & ATTR_DONTSAVE)
1204             && ($attr->{flags} & ATTR_AUTOCREATE) ) {
1205 1         1 my $type = $attr->{type};
1206 1         3 my $snapshot = {};
1207 1         3 foreach my $key (keys %$attr) {
1208 5 0 0     27 $snapshot->{$key} = (
    0 0        
    0          
    50          
    50          
    100          
1209             $key =~ /^(value|real_value)$/ ? (
1210             $type =~ /^(int|number)$/ ? (
1211             defined($attr->{minimum}) ?
1212             $attr->{minimum} : 0
1213             ) :
1214             $type eq 'string' ? '' :
1215             $type eq 'any' &&
1216             ref($attr->{$key}) eq 'ARRAY' ? [ ] :
1217             $type eq 'any' &&
1218             ref($attr->{$key}) eq 'HASH' ? { } :
1219             undef
1220             ) :
1221             $attr->{$key}
1222             );
1223             }
1224 1         4 $obj->{snapshots}{$aname} = $snapshot;
1225             }
1226              
1227             # Done.
1228 140         515 $obj;
1229             }
1230              
1231             # Delete an attribute. Note that this will delete only on the current object
1232             # and not inherited attributes.
1233              
1234             sub del_attr
1235             {
1236 58     58 0 106 my $obj = shift;
1237 58         88 my ($aname) = @_;
1238              
1239             # Do nothing if the attribute does not exist.
1240 58 100       215 return 0 if (!defined($obj->{attr}{$aname}));
1241              
1242             # Delete the attribute.
1243 2         10 delete $obj->{attr}{$aname};
1244              
1245             # Done.
1246 2         12 1;
1247             }
1248              
1249             # Check to see if an attribute exists.
1250              
1251             sub attr_exists
1252             {
1253 13     13 0 227 my ($obj, $aname) = @_;
1254 13         38 my $attr = $obj->_find_attr($aname);
1255              
1256 13         68 defined($attr);
1257             }
1258              
1259             # Check specifically that the attribute exists on this object and don't
1260             # consider inheritance.
1261              
1262             sub attr_exists_here
1263             {
1264 6     6 0 17 my ($obj, $aname) = @_;
1265              
1266 6         46 defined($obj->{attr}{$aname});
1267             }
1268              
1269             # Fetch value or properties of an attribute
1270              
1271             sub attr
1272             {
1273 962     962 0 4709 my ($obj, $aname, $prop) = @_;
1274 962 100       2017 $prop = 'value' if (!defined($prop));
1275              
1276             # If the attribute does not exist, simply return undef.
1277 962         1889 my $attr = $obj->_find_attr($aname);
1278 962 100       2286 return undef if (!defined($attr));
1279              
1280             # Check to see if the property exists.
1281 706 50       1575 croak("Attribute '$aname' does not have property called '$prop'")
1282             if (!defined($attr->{$prop}));
1283              
1284             # The value and real_value are special cases.
1285 706 50       2271 if ($prop =~ /^(value|real_value)$/) {
1286 706         741 my $result;
1287 706 100 100     3599 if ($attr->{type} eq 'int' && $attr->{track_fractional}) {
    100 66        
      100        
1288             # The value that the caller really sees is the integer.
1289 5         10 my $expr = '$result = ' . $attr->{on_fractional} .
1290             '($attr->{$prop})';
1291 5         296 eval($expr);
1292             } elsif ($attr->{type} eq 'string'
1293             && defined($attr->{map})
1294             && defined($attr->{map}{$attr->{$prop}}) ) {
1295             # Return the mapped value
1296 3         10 $result = $attr->{map}{$attr->{$prop}};
1297             } else {
1298             # Return whatever is there.
1299 698         1123 $result = $attr->{$prop};
1300             }
1301             # If this value is OOB, this must mean a force was done on a
1302             # mod_attr or the mode was set to 'track', so make sure we return
1303             # only a value within the bounds.
1304 706 50 66     1744 $result = $attr->{minimum}
1305             if (defined($attr->{minimum}) && $result < $attr->{minimum});
1306 706 50 66     1683 $result = $attr->{maximum}
1307             if (defined($attr->{maximum}) && $result > $attr->{maximum});
1308 706         2071 $result;
1309             } else {
1310             # No interpretation of the value needed.
1311 0         0 $attr->{$prop};
1312             }
1313             }
1314              
1315             # Fetch the "raw" attribute property value. This bypasses the code that checks
1316             # for fractional interpretations and mapping.
1317              
1318             sub raw_attr
1319             {
1320 10     10 0 36 my ($obj, $aname, $prop) = @_;
1321 10 50       30 $prop = 'value' if (!defined($prop));
1322              
1323             # Check to see if attribute exists.
1324 10         28 my $attr = $obj->_find_attr($aname);
1325 10 50       32 return undef if (!defined($attr));
1326              
1327             # Check to see if the property exists.
1328 10 50       35 croak("Attribute '$aname' does not have property called '$prop'")
1329             if (!defined($attr->{$prop}));
1330              
1331             # Return the value of the property.
1332 10         55 $attr->{$prop};
1333             }
1334              
1335             # Fetch the reference to an attribute.
1336              
1337             sub attr_ref
1338             {
1339 0     0 0 0 my ($obj, $aname, $prop) = @_;
1340              
1341 0 0       0 $prop = 'value' if (!defined($prop));
1342 0         0 my $attr = $obj->_find_attr($aname);
1343 0 0       0 if (defined($attr)) {
1344 0 0       0 defined($attr->{$prop}) ? \$attr->{$prop} : undef;
1345             } else {
1346 0         0 carp "WARNING: Attempt to get reference to '$prop' of " .
1347             "non-existent attribute '$aname'";
1348 0         0 undef;
1349             }
1350             }
1351              
1352             # Modify an attribute
1353              
1354             sub mod_attr
1355             {
1356 84     84 0 3926 my $obj = shift;
1357 84         275 my %args = @_;
1358              
1359             # Check for a cancel operation.
1360 84         623 FetchParams(\@_, \%args, [
1361             [ 'opt', 'cancel_modify', undef, 'string' ],
1362             [ 'opt', 'cancel_modify_re', undef, 'string' ],
1363             [ 'opt', 'immediate', 0, 'boolean' ],
1364             ]);
1365 84 100       371 if (defined($args{cancel_modify})) {
1366             # Normal cancel
1367 4         7 my $id = $args{cancel_modify};
1368 4 50       14 if (defined($obj->{pmod}{$id})) {
1369              
1370             # First check to see if the mod was incremental. If not,
1371             # then we need to reverse the change that it had effected.
1372 4         8 my $mod = $obj->{pmod}{$id};
1373 4         8 my $aname = $mod->{aname};
1374 4 50       11 if (!$mod->{incremental}) {
1375             # Call myself to do the change. NOTE: We specify "other"
1376             # as myself. Why? Because whatever was causing the original
1377             # modification (i.e. the original "other") is no longer
1378             # apropos, since the change it initiated is no longer
1379             # present. One can think of the object itself now putting
1380             # back the original value.
1381 4         16 my %opts = ( -name => $aname, -other => $obj );
1382 4 100       14 $opts{modify} = -$mod->{modify}
1383             if (defined($mod->{modify}));
1384 4 100       12 $opts{modify_real} = -$mod->{modify_real}
1385             if (defined($mod->{modify_real}));
1386             # By default, we queue this up and do it at next process(),
1387             # to be consistent with the way modifiers are applied.
1388             # Specifying an immediate of true forces us to do it now.
1389 4 100       10 if ($args{immediate}) {
1390 1         12 $obj->mod_attr(%opts);
1391             } else {
1392 3         18 $obj->queue('mod_attr', %opts);
1393             }
1394             }
1395 4         12 delete $obj->{pmod}{$id};
1396 4         7 $obj->{pmod_active}--;
1397 4 50       14 $obj->{pmod_next} = 0 if ($obj->{pmod_active} == 0);
1398 4         32 return 1;
1399             } else {
1400 0         0 return 0;
1401             }
1402             }
1403 80 100       192 if (defined($args{cancel_modify_re})) {
1404             # Cancel all that match the regular expression. We do this by
1405             # building a list of matching modifiers and call ourself for each.
1406 1         3 my $re = $args{cancel_modify_re};
1407 1         2 my @ids = grep { /$re/ } keys %{$obj->{pmod}};
  5         49  
  1         5  
1408 1         3 delete $args{cancel_modify_re};
1409 1         3 foreach my $id (@ids) {
1410 2         3 $args{cancel_modify} = $id;
1411 2         7 $obj->mod_attr(%args);
1412             }
1413 1         12 return scalar(@ids);
1414             }
1415              
1416             # The first thing we need to is actually find the attribute. If the
1417             # attribute cannot be found on this object, we check to see if it
1418             # has an inheritance, and keep checking up the inheritance tree until
1419             # we find it.
1420 79         423 FetchParams(\@_, \%args, [
1421             [ 'req', 'name' ],
1422             ], 1 );
1423 79         194 my $aname = $args{name};
1424 79         246 my ($attr, $inherited) = $obj->_find_attr($aname);
1425 79 0 33     200 croak("Attempt to modify unknown attribute '$aname' " .
1426             "on object $obj->{id}") if (!defined($attr) && !$inherited);
1427 79 50 33     243 croak("Attempt to modify attribute '$aname' that could not be " .
1428             "inherited") if (!defined($attr) && $inherited);
1429              
1430             # Check for attempt to modify static attribute.
1431 79 50       599 croak("Attempt to modify static attr '$aname' on '$obj->{id}' " .
    100          
1432             ( $inherited ? "(inherited)" : "(not inherited)" ) )
1433             if ($attr->{flags} & ATTR_STATIC);
1434              
1435             # If we inherited this attribute, then clone it so that we have
1436             # our own copy. We do this via a clever trick: Using IO::String
1437             # to create a stringified version of the data.
1438 77 100       172 if ($inherited) {
1439 1         6 $obj->{attr}{$aname} = {};
1440 1         13 my $iostr = IO::String->new();
1441 1         66 SaveData($iostr, $attr);
1442 1         7 seek $iostr, 0, 0;
1443 1         28 LoadData($iostr, $obj->{attr}{$aname});
1444 1         9 $attr = $obj->{attr}{$aname};
1445             }
1446              
1447             # Fetch basic modifier parameters.
1448 77         202 %args = ();
1449 77 100 100     536 my $vtype = ( defined($attr->{values}) ?
    100          
    50          
1450             $attr->{values} :
1451             $attr->{type} eq 'int' && $attr->{track_fractional} ?
1452             'number' :
1453             $attr->{type} eq 'object' ?
1454             'any' :
1455             $attr->{type}
1456             );
1457 77         1147 FetchParams(\@_, \%args, [
1458             [ 'opt', 'minimum', undef, $vtype ],
1459             [ 'opt', 'maximum', undef, $vtype ],
1460             [ 'opt', 'out_of_bounds', undef, [ qw(ignore use_up track) ] ],
1461             [ 'opt', 'tend_to_rate', undef, $vtype ],
1462             [ 'opt', 'priority', undef, 'int' ],
1463             [ 'opt', 'flags', undef, 'int' ],
1464             [ 'opt', 'value', undef, $vtype ],
1465             [ 'opt', 'real_value', undef, $vtype ],
1466             [ 'opt', 'modify', undef, $vtype ],
1467             [ 'opt', 'modify_real', undef, $vtype ],
1468             [ 'opt', 'object', undef, 'object' ],
1469             [ 'opt', 'other', undef, 'object' ],
1470             ] );
1471              
1472             # Check for property modifiers first.
1473 77         437 my $pcount = 0;
1474 77         154 foreach my $prop (qw(minimum maximum on_fractional out_of_bounds
1475             tend_to_rate priority flags)) {
1476 539 100       1176 next if (!defined($args{$prop}));
1477 3 50       21 croak("Property '$prop' allowed only on numeric attribute")
1478             if ($vtype !~ /^(int|number)$/);
1479 3         11 $attr->{$prop} = delete $args{$prop};
1480 3         9 $pcount++;
1481             }
1482              
1483             # If at least one property set, we're allowed not to have any
1484             # modification parameters.
1485 77         137 my $acount = scalar(keys(%args));
1486 77 100 100     214 return 1 if ($pcount > 0 && $acount == 0);
1487              
1488             # Check for mod parameters
1489 75 50       159 croak("No modification parameter present") if ($acount == 0);
1490 75 50 66     407 croak("Cannot combine attribute absolute set and modification " .
      33        
      66        
1491             "in single mod_attr() call")
1492             if ( (defined($args{value}) || defined($args{real_value}))
1493             && (defined($args{modify}) || defined($args{modify_real})) );
1494 75 50 66     380 croak("Cannot set/modify real value when value not split")
      66        
1495             if ( (defined($args{real_value}) || defined($args{modify_real}))
1496             && !defined($attr->{real_value}) );
1497              
1498             # Check for a simple set operation.
1499 75 100 66     311 if (defined($args{value}) || defined($args{real_value})) {
1500              
1501             # Yes, value is being set. Fetch all optional parameters.
1502 11         66 FetchParams(\@_, \%args, [
1503             [ 'opt', 'force', 0, 'boolean' ],
1504             [ 'opt', 'defer', 0, 'boolean' ],
1505             [ 'opt', 'no_tend_to', 0, 'boolean' ],
1506             ] );
1507              
1508             # Deferred? If so, queue it and we're done.
1509 11 50       42 if ($args{defer}) {
1510 0         0 delete $args{defer};
1511 0         0 $args{name} = $aname;
1512 0         0 $obj->queue('mod_attr', %args);
1513 0         0 return 1;
1514             }
1515              
1516             # If dropped down to here, then this is to be done right now.
1517 11         40 $obj->_set_attr($aname, %args);
1518              
1519             } else {
1520              
1521             # No, this is a modification relative to the current value of
1522             # the attribute. This is allowed only for numeric types.
1523 64 50       293 croak("Attempt a relative modify on non-numeric attribute " .
1524             "'$aname' of '$obj->{id}'")
1525             if ($attr->{type} !~ /^(int|number)$/);
1526              
1527             # Fetch all possible parameters.
1528 64         613 FetchParams(\@_, \%args, [
1529             [ 'opt', 'persist_as', undef, 'string' ],
1530             [ 'opt', 'priority', 0, 'int' ],
1531             [ 'opt', 'time', undef, 'int' ],
1532             [ 'opt', 'delay', 0, 'int' ],
1533             [ 'opt', 'force', 0, 'boolean' ],
1534             [ 'opt', 'incremental', 0, 'boolean' ],
1535             [ 'opt', 'apply_now', 0, 'boolean' ],
1536             ] );
1537              
1538             # Is to be persistent?
1539 64         256 my ($id, $was_pmod, $mod);
1540 64 100       167 if ($args{persist_as}) {
1541              
1542             # Yes, so don't do the change right now. Simply add it as
1543             # a new persistent modifier (pmod). If one already exists,
1544             # then replace it silently. The index value is used in sorting,
1545             # so that when pmods of equal priority are placed in the object,
1546             # they are guaranteed to run in the order they were created.
1547             #
1548             # Note that we store the "other" and "object" parameters as the
1549             # object ID rather than the actual object ref itself.
1550 10         18 $id = $args{persist_as};
1551 10         27 $was_pmod = defined($obj->{pmod}{$id});
1552 10 50       119 $mod = {
1553             aname => $aname,
1554             index => ( $was_pmod ?
1555             $obj->{pmod}{$id}{index} :
1556             $obj->{pmod_next}++ ),
1557             priority => $args{priority},
1558             time => $args{time},
1559             delay => $args{delay},
1560             force => $args{force},
1561             modify => $args{modify},
1562             modify_real => $args{modify_real},
1563             incremental => $args{incremental},
1564             applied => 0,
1565             locked => 0,
1566             };
1567 10 100       33 $mod->{other} = $args{other}->id() if ($args{other});
1568 10 50       26 $mod->{object} = $args{object}->id() if ($args{object});
1569 10         28 $obj->{pmod}{$id} = $mod;
1570 10 50       29 $obj->{pmod_active}++ unless ($was_pmod);
1571              
1572             }
1573              
1574 64 100 100     239 if (!$args{persist_as} || $args{apply_now}) {
1575              
1576             # Either this is NOT a persistent mod, or it IS, but the
1577             # user wants to force the change to be applied right now.
1578 55 100       226 $args{value} = $attr->{value} + $args{modify}
1579             if (defined($args{modify}));
1580 55 100       203 $args{real_value} = $attr->{real_value} + $args{modify_real}
1581             if (defined($args{modify_real}));
1582 55         257 $obj->_set_attr($aname, %args);
1583              
1584             # And if it is a persistent mod, make sure it does not
1585             # get applied twice.
1586 55 100       229 $mod->{applied} = 1 if (defined($args{persist_as}));
1587              
1588             }
1589              
1590             } # if defined($args{value}) || defined($args{real_value})
1591              
1592 74         584 1;
1593             }
1594              
1595             ####
1596             ## QUEUING AND CALLBACK CONTROL
1597              
1598             # Invoke a callback or an array of callbacks on object.
1599              
1600             sub invoke_callbacks
1601             {
1602 197     197 0 264 my $self = shift;
1603 197         746 my %args = ();
1604              
1605             # Fetch parameters. Note that all parameters are optional. This is OK,
1606             # but watch how you define your callbacks. If you have a callback that
1607             # has "O:other" as the target but no 'other' parameter was passed, this
1608             # will bomb.
1609 197         1725 FetchParams(\@_, \%args, [
1610             [ 'opt', 'other', undef, 'object' ],
1611             [ 'opt', 'object', undef, 'object' ],
1612             [ 'opt', 'action', undef, 'string' ],
1613             [ 'opt', 'callback', undef, 'callback' ],
1614             [ 'opt', 'args', {}, 'hashref' ],
1615             [ 'opt', 'flags', 0, 'int' ],
1616             ] );
1617 197         798 my $other = $args{other};
1618 197         258 my $object = $args{object};
1619 197         278 my $action = $args{action};
1620 197         303 my $callback = $args{callback};
1621 197         234 my $aargs = $args{args};
1622 197         257 my $flags = $args{flags};
1623              
1624             # If the callback is undefined, this counts as success.
1625 197 50       394 return 1 if (!$callback);
1626              
1627             # If this is a list of callbacks rather than a callback itself, then
1628             # invoke myself with each individual callback. Stop at any time we
1629             # receive a return of false from a callback.
1630 197         510 my @cargs = @$callback;
1631 197 100       397 if (ref($cargs[0]) eq 'ARRAY') {
1632 33         46 my $rc = 0;
1633 33         37 my $nocheck = 0;
1634 33         95 while (my $callback = shift(@cargs)) {
1635             # Check for special flags and commands.
1636 95 100       231 if (!ref($callback)) {
1637 5 50       9 if ($callback eq 'FAIL') {
    0          
    0          
1638             # Next item is a failure callback, so skip it, since
1639             # we already know the previous one succeeded.
1640 5         7 shift @cargs;
1641             } elsif ($callback eq 'NOCHECK') {
1642             # Stop checking return codes and execute everything
1643             # regardless (i.e. assume true return for each)
1644 0         0 $nocheck = 1;
1645             } elsif ($callback eq 'CHECK') {
1646             # Turn return code checking back on.
1647 0         0 $nocheck = 0;
1648             }
1649 5         11 next;
1650             }
1651             # Invoke.
1652 90         259 $rc = $self->invoke_callbacks(
1653             other => $other,
1654             object => $object,
1655             flags => $flags,
1656             action => $action,
1657             callback => $callback,
1658             args => $aargs,
1659             );
1660             # Force success if NOCHECK is on.
1661 89 50       586 $rc = 1 if ($nocheck);
1662             # If the callback failed, we will stop. But before that, see
1663             # if the next item is a failure callback and execute it if
1664             # so. We do NOT return the return value of these callbacks.
1665             # We return the boolean false from the original non-failure
1666             # callbacks to indicate that a failure indeed occurred.
1667 89 100       326 if (!$rc) {
1668 2 100 66     16 if (@cargs && !ref($cargs[0]) && $cargs[0] eq 'FAIL') {
      66        
1669 1         2 shift @cargs;
1670 1         1 $callback = shift @cargs;
1671 1         3 $self->invoke_callbacks(
1672             other => $other,
1673             object => $object,
1674             flags => $flags,
1675             action => $action,
1676             callback => $callback,
1677             args => $aargs,
1678             );
1679             }
1680 2         9 last;
1681             }
1682             }
1683 32         207 $rc;
1684             } else {
1685 164         203 my $oname = shift @cargs;
1686 164 100       463 my $obj = (
    100          
    50          
    100          
    100          
1687             $oname eq 'O:self' ? $self :
1688             $oname eq 'O:other' ? $other :
1689             $oname eq 'O:object' ? $object :
1690             $oname eq 'O:manager' ? $self->manager() :
1691             $oname =~ /^O:(.+)$/ ? $self->find($1) :
1692             $oname
1693             );
1694             # If the object was not found, look at the flags. If the MISSING_OK
1695             # flag is there, skip callback and return success, otherwise
1696             # return 0 to abort this list of callbacks.
1697 164 100       354 if (!$obj) {
1698 2 100       9 return 1 if ($flags & ACT_MISSING_OK);
1699 1         236 croak("Object '$oname' not found in '$action' trigger " .
1700             "on $self->{id}");
1701             }
1702             # Now scan the arguments list and perform substitutions. Any
1703             # arg that starts with "A:" represents an arg to be retrieved
1704             # from either the $aargs list, or from the callback args (such
1705             # as self, other, etc).
1706 162         258 foreach my $arg (@cargs) {
1707             # For performance reasons, check to see if any substitution is
1708             # even needed.
1709 622 100       1811 next if ($arg !~ /[AO]:/);
1710             # Now check for complete substitutions
1711 359         347 my $narg;
1712 359 50 100     2196 $narg = (
    50          
    100          
    100          
    100          
    100          
    100          
1713             $arg =~ /^A:([a-zA-Z0-9_]+)$/ &&
1714             defined($aargs->{$1}) ? $aargs->{$1} :
1715             $arg eq 'A:action' ? $action :
1716             $arg eq 'O:self' ? $self :
1717             $arg eq 'O:other' ? $other :
1718             $arg eq 'O:object' ? $object :
1719             $arg eq 'O:manager' ? $self->manager() :
1720             $arg =~ /^O:([a-zA-Z0-9_]+$)/
1721             ? $self->find($1)
1722             : undef );
1723             # If we found something, then set it and done.
1724 359 100       671 if (defined($narg)) {
1725 335         456 $arg = $narg;
1726 335         517 next;
1727             }
1728             # Otherwise, we do a full substitution and eval() on it.
1729 24         91 while ( $arg =~ /([OA]:[a-zA-Z0-9_]+)/ ) {
1730 29         55 my $subarg = $1;
1731 29 0 33     177 my $subval = (
    50          
    100          
    100          
    100          
    50          
    50          
1732             $subarg =~ /^A:([a-zA-Z0-9_]+)$/ &&
1733             defined($aargs->{$1}) ? "'$aargs->{$1}'" :
1734             $subarg eq 'A:action' ? "'$action'" :
1735             $subarg eq 'O:self' ? '$self' :
1736             $subarg eq 'O:other' ? '$other' :
1737             $subarg eq 'O:object' ? '$object' :
1738             $subarg eq 'O:manager' ? '$self->manager()' :
1739             $subarg =~ /^O:([a-zA-Z0-9_]+$)/
1740             ? '$self->find($1)'
1741             : 'undef' );
1742 29         468 $arg =~ s/$subarg/$subval/g;
1743             }
1744 24         1605 my $val = eval($arg);
1745 24 50       91 croak "Failed on eval of arg expression << $arg >>: $@" if ($@);
1746 24         54 $arg = $val;
1747             }
1748             # Invoke.
1749 162 100       390 if (!ref($obj)) {
1750             # The user specified a name of a subroutine instead.
1751 10     10   141 no strict 'refs';
  10         25  
  10         29479  
1752 4         20 &$obj(@cargs);
1753             } else {
1754             # Object reference, so the next item is a method name. Note
1755             # that this means you can do fancy things like specify the
1756             # method name as an "A:*" specifier and thus have the method
1757             # called defined in the args.
1758 158         231 my $meth = shift @cargs;
1759 158         598 $obj->$meth(@cargs);
1760             }
1761             }
1762             }
1763              
1764             # Queue an action to be run when the object is processed. This must take the
1765             # form of a method name that can be invoked with the object reference. This is
1766             # so this data can be properly saved to an external file (CODE refs don't save
1767             # properly). In fact, none of the args to the action can be references. The
1768             # exception is that you can specify a reference to a Games::Object object
1769             # or one subclassed from it. This is translated to a form that can be written
1770             # to the file and read back again (via the unique object ID).
1771             #
1772             # FIXME: Currently this is a black hole. Actions that go in do not come out
1773             # (i.e. they cannot be deleted or told not to run) unless the object is
1774             # deleted.
1775              
1776             sub queue
1777             {
1778 7     7 0 19 my ($obj, $method, @args) = @_;
1779              
1780             # The method must be valid.
1781 7 50       47 croak("Attempt to queue action for '$obj->{id}' with non-existent " .
1782             "method name '$method'") if (!$obj->can($method));
1783              
1784             # Examine the args. If any args are object refs derived from
1785             # Games::Object, replace with their IDs instead, in case the object
1786             # gets save()d before the queue is executed.
1787 7         20 foreach my $aindex (0 .. $#args) {
1788 34 100       52 if (_IsObject($args[$aindex])) {
1789 3         4 my $qindex = @{$obj->{queue}};
  3         8  
1790 3         14 $args[$aindex] = $args[$aindex]->id();
1791 3         17 $obj->{queue_changed}{$qindex}{$aindex} = "GO::id";
1792             }
1793             }
1794              
1795             # Okay to be queued.
1796 7         11 push @{$obj->{queue}}, [ $method, @args ];
  7         23  
1797 7         18 1;
1798             }
1799              
1800             # Process an action.
1801              
1802             sub action
1803             {
1804 245     245 1 363 my $self = shift;
1805 245         446 my %args = ();
1806              
1807             # Fetch parameters.
1808 245         2076 FetchParams(\@_, \%args, [
1809             [ 'opt', 'other', undef, 'object' ],
1810             [ 'opt', 'object', undef, 'object' ],
1811             [ 'req', 'action', undef, 'string' ],
1812             [ 'opt', 'args', {}, 'hashref' ],
1813             [ 'opt', 'flags', 0, 'int' ],
1814             ] );
1815 245         1019 my $other = $args{other};
1816 245         331 my $object = $args{object};
1817 245         328 my $action = $args{action};
1818 245         324 my $aargs = $args{args};
1819 245         312 my $flags = $args{flags};
1820              
1821             # Find the callback
1822 245         264 my $callback;
1823 245 100       1084 if ($action =~ /^attr:(.+):(.+)$/) {
    100          
    50          
1824              
1825             # Attribute-based action.
1826 105         184 my $aname = $1;
1827 105         178 my $oname = $2;
1828 105         241 my $attr = $self->_find_attr($aname);
1829 105 100 66     557 $callback = $attr->{$oname}
1830             if (defined($attr) && exists($attr->{$oname}));
1831 105 100       275 $flags |= $attr->{flags} if ($callback);
1832              
1833             } elsif ($action =~ /^flag:(.+):(.+)$/) {
1834              
1835             # Attribute-based action.
1836 8         15 my $fname = $1;
1837 8         23 my $oname = $2;
1838 8         16 my $flag = $self->_find_flag($fname);
1839 8 100 66     47 $callback = $flag->{$oname}
1840             if (defined($flag) && exists($flag->{$oname}));
1841 8 100       20 $flags |= $flag->{flags} if ($callback);
1842              
1843             } elsif ($action =~ /^object:(.+)$/) {
1844              
1845             # Object-based action.
1846 132         237 my $oname = $1;
1847 132         383 $callback = $self->attr("_ACT_${oname}");
1848              
1849             } else {
1850              
1851 0         0 croak("Undefined action syntax '$action'");
1852              
1853             }
1854              
1855             # Do nothing (successfully) if no callback was found.
1856 245 100       1078 return 1 if (!$callback);
1857              
1858             # Otherwise invoke the callback and return its value.
1859 106         444 $self->invoke_callbacks(
1860             other => $other,
1861             object => $object,
1862             action => $action,
1863             callback => $callback,
1864             args => $aargs,
1865             flags => $flags,
1866             );
1867             }
1868              
1869             ####
1870             ## OBJECT PROCESSING METHODS
1871              
1872             # Process an object. This is used to do such actions as executing pending
1873             # actions on the queue, updating attributes, and so on. The real work is
1874             # farmed out to other methods, and the @process_list array tells us which
1875             # to call, or the user can pass in a different list.
1876             #
1877             # Note that we do not allow methods to be called recursively.
1878              
1879             sub process
1880             {
1881 41     41 1 2012 my ($obj, $plist) = @_;
1882              
1883 41 100       112 $plist = \@process_list if (!$plist);
1884 41         70 foreach my $method (@process_list) {
1885 123         267 $obj->_lock_method($method);
1886 123         366 $obj->$method();
1887 123         281 $obj->_unlock_method($method);
1888             }
1889 41         116 1;
1890             }
1891              
1892             # Process all items on the object's queue until the queue is empty. To
1893             # praction potential endless loops (routine A runs, places B on the queue,
1894             # routine B runs, places A on the queue, etc), we track how many times we
1895             # saw a given method, and if it reaches a critical threshhold, we issue a
1896             # warning and do not execute that routine any more this time through. This
1897             # is controlled by the $process_limit variable.
1898              
1899             sub process_queue
1900             {
1901 41     41 1 59 my $obj = shift;
1902 41         74 my $queue = $obj->{queue};
1903 41         71 my %mcount = ();
1904              
1905 41         51 my $qindex = 0;
1906 41         111 while (@$queue) {
1907 7         11 my $callbk = shift @$queue;
1908 7         21 my ($meth, @args) = @$callbk;
1909 7 100 100     35 if (defined($obj->{queue_changed})
1910             && defined($obj->{queue_changed}{$qindex}) ) {
1911             # Some args were changed, so set them back.
1912 2         4 my $changed = delete $obj->{queue_changed}{$qindex};
1913 2         6 foreach my $aindex (keys %$changed) {
1914 2         5 my $change = $changed->{$aindex};
1915 2 50       5 if ($change eq 'GO::id') {
1916 2         9 $args[$aindex] = $obj->find($args[$aindex]);
1917             } else {
1918 0         0 croak "Unknown queue arg change type '$change'";
1919             }
1920             }
1921             }
1922 7 100       24 $mcount{$meth} = 0 if (!defined($mcount{$meth}));
1923 7 50       35 if ($mcount{$meth} > $process_limit) {
    50          
1924             # Already gave a warning on this, so ignore it silently.
1925 0         0 next;
1926             } elsif ($mcount{$meth} == $process_limit) {
1927             # Just reached it last time through, so issue warning.
1928 0         0 carp("Number of calls to '$meth' has reached processing " .
1929             "limit of $process_limit for '$obj->{id}', will no " .
1930             "longer invoke this method this time through queue " .
1931             "(you may have an endless logic loop somewhere)");
1932 0         0 next;
1933             }
1934 7         10 $mcount{$meth}++;
1935 7         27 $obj->$meth(@args);
1936             }
1937              
1938 41         73 1;
1939             }
1940              
1941             # Process all tend_to rates in attributes that have them.
1942              
1943             sub process_tend_to
1944             {
1945 41     41 1 56 my $obj = shift;
1946 148         374 my @anames = sort { $obj->{attr}{$b}{priority} <=>
  41         197  
1947 41         53 $obj->{attr}{$a}{priority} } keys %{$obj->{attr}};
1948              
1949 41         86 foreach my $aname (@anames) {
1950              
1951             # Skip if not applicable
1952 130         214 my $attr = $obj->{attr}{$aname};
1953 130 100       338 next if (!defined($attr->{tend_to_rate}));
1954              
1955             # Get the new value.
1956 43         60 my $inc = $attr->{tend_to_rate};
1957 43         50 my $new = $attr->{value};
1958 43         55 my $target = $attr->{real_value};
1959 43 100       101 if ($new < $target) {
    50          
1960 37         45 $new += $inc;
1961 37 50       149 $new = $target if ($new > $target);
1962             } elsif ($new > $target) {
1963 6         11 $new -= $inc;
1964 6 50       18 $new = $target if ($new < $target);
1965             } else {
1966             # Nothing to do.
1967 0         0 next;
1968             }
1969              
1970             # Set to the new value. Note that we specify the "other" object
1971             # as ourselves, since the source of the change is ourself.
1972 43         114 $obj->_set_attr($aname,
1973             value => $new,
1974             force => 1,
1975             other => $obj);
1976              
1977             }
1978              
1979 41         79 1;
1980             }
1981              
1982             # Process persistent modifications.
1983              
1984             sub process_pmod
1985             {
1986 41     41 1 55 my $obj = shift;
1987 34         61 my @ids = sort {
1988 41         174 my $amod = $obj->{pmod}{$a};
1989 34         54 my $bmod = $obj->{pmod}{$b};
1990 34 50       79 if ($amod->{priority} == $bmod->{priority}) {
1991 34         103 $amod->{index} <=> $bmod->{index};
1992             } else {
1993 0         0 $bmod->{priority} <=> $amod->{priority};
1994             }
1995 41         52 } keys %{$obj->{pmod}};
1996              
1997 41         83 foreach my $id (@ids) {
1998              
1999 43         76 my $mod = $obj->{pmod}{$id};
2000 43         62 my $aname = $mod->{aname};
2001 43         71 my $attr = $obj->{attr}{$aname};
2002 43 50 100     359 if ($mod->{locked}) {
    50 100        
    100          
    100          
2003              
2004             # Locked. Simply unlock so it can run next time.
2005 0         0 $mod->{locked} = 0;
2006              
2007             } elsif ($mod->{delay} > 0) {
2008              
2009             # Delay factor. Decrement and done.
2010 0         0 $mod->{delay}--;
2011              
2012             } elsif (defined($mod->{time}) && $mod->{time} <= 0) {
2013              
2014             # Time is up, so cancel this one.
2015 1         7 $obj->mod_attr(-name => $aname,
2016             -cancel_modify => $id,
2017             -immediate => 1);
2018              
2019             } elsif ($mod->{applied} && !$mod->{incremental}) {
2020              
2021             # This is a non-incremental modifier that was applied already,
2022             # so simply count down the time if applicable.
2023 24 100       79 $mod->{time}-- if (defined($mod->{time}));
2024              
2025             } else {
2026              
2027             # Change has not yet been applied or this is an incremental
2028             # change, so apply it.
2029 18         104 my %args = (
2030             -name => $aname,
2031             -force => $mod->{force},
2032             -other => $obj->find($mod->{other}),
2033             -object => $obj->find($mod->{object}),
2034             );
2035 18 100       77 $args{modify} = $mod->{modify}
2036             if (defined($mod->{modify}));
2037 18 100       57 $args{modify_real} = $mod->{modify_real}
2038             if (defined($mod->{modify_real}));
2039 18         72 $obj->mod_attr(%args);
2040 18         39 $mod->{applied} = 1;
2041              
2042             # Count down the time if applicable
2043 18 100       89 $mod->{time}-- if (defined($mod->{time}));
2044              
2045             }
2046             }
2047              
2048 41         74 1;
2049             }
2050              
2051             ####
2052             ## MISCELLANEOUS OBJECT METHODS
2053              
2054             # Fetch/change the ID of object. Changing the ID may fail if the object is
2055             # managed and the manager does not like the new ID.
2056              
2057             sub id
2058             {
2059 637     637 0 7289 my ($obj, $id) = @_;
2060              
2061 637 100       1135 if (defined($id)) {
2062 32         108 my $man = $obj->manager();
2063 32 50       72 $man->id($obj, $id) if ($man);
2064 32         102 $obj->{id} = $id;
2065             } else {
2066 605         2055 $obj->{id};
2067             }
2068             }
2069              
2070             # Fetch/set manager of object. Note that there is a difference between not
2071             # specifying a manager parameter at all and specifying undef:
2072             #
2073             # $obj->manager($man) - Sets the manager to object $man
2074             # $obj->manager(undef) - Clears the old manager setting without setting
2075             # a new one.
2076             # $obj->manager() - Returns the current manager setting
2077              
2078             sub manager
2079             {
2080 706     706 1 967 my ($obj, $man) = @_;
2081              
2082 706 100       1367 if (@_ == 2) {
2083 41         157 $obj->del_attr(ANAME_MANAGER);
2084 41 100       232 $obj->new_attr(
2085             name => ANAME_MANAGER,
2086             type => 'any',
2087             value => $man,
2088             flags => ATTR_DONTSAVE | ATTR_NO_INHERIT,
2089             ) if ($man);
2090             } else {
2091 665         1352 $obj->attr(ANAME_MANAGER);
2092             }
2093             }
2094              
2095             # Fetch/set priority of object.
2096              
2097             sub priority
2098             {
2099 81     81 1 116 my $obj = shift;
2100              
2101 81 100       143 if (@_) {
2102 9         11 my $pri = shift;
2103 9 100       64 $highest_pri = $pri if ($pri >= $highest_pri);
2104 9         15 my $oldpri = $obj->{priority};
2105 9         14 $obj->{priority} = $pri;
2106 9         18 $oldpri;
2107             } else {
2108 72         227 $obj->{priority};
2109             }
2110             }
2111              
2112             ####
2113             ## DESTRUCTORS
2114              
2115             # Destroy the object and remove it from its manager's table. The caller can
2116             # pass in optional arbitrary parameters that are passed to any action binding.
2117              
2118             sub destroy
2119             {
2120 0     0 0   my $obj = shift;
2121 0           my %aargs = ();
2122              
2123             # Fetch parameters.
2124 0           FetchParams(\@_, \%aargs, [
2125             [ 'opt', 'other', undef, 'object' ],
2126             [ 'opt', 'object', undef, 'object' ],
2127             [ 'opt', 'args', {}, 'hashref' ],
2128             ] );
2129              
2130             # Check to see if we have an attribute table. If not present, we
2131             # did this already.
2132 0 0         return 0 if (!defined($obj->{attr}));
2133              
2134             # Trigger action BEFORE deletion so that the action code can examine
2135             # the object
2136 0           my $id = $obj->{id};
2137 0           $aargs{action} = 'object:on_destroy';
2138 0           $obj->action(%aargs);
2139              
2140             # Remove from manager, if applicable
2141 0           my $man = $obj->manager();
2142 0 0         $man->remove($obj->{id}) if ($man);
2143              
2144             # Delete all keys so that it can no longer be used. This should free
2145             # up all references to other objects.
2146 0           foreach my $key (keys %$obj) {
2147 0           delete $obj->{$key};
2148             }
2149              
2150             # Done.
2151 0           1;
2152             }
2153              
2154             1;