LCOV - code coverage report
Current view: top level - Core - AI.m (source / functions) Hit Total Coverage
Test: coverxygen.info Lines: 0 42 0.0 %
Date: 2025-05-28 07:50:54 Functions: 0 0 -

          Line data    Source code
       1           0 : /*
       2             : 
       3             : AI.m
       4             : 
       5             : Oolite
       6             : Copyright (C) 2004-2013 Giles C Williams and contributors
       7             : 
       8             : This program is free software; you can redistribute it and/or
       9             : modify it under the terms of the GNU General Public License
      10             : as published by the Free Software Foundation; either version 2
      11             : of the License, or (at your option) any later version.
      12             : 
      13             : This program is distributed in the hope that it will be useful,
      14             : but WITHOUT ANY WARRANTY; without even the implied warranty of
      15             : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      16             : GNU General Public License for more details.
      17             : 
      18             : You should have received a copy of the GNU General Public License
      19             : along with this program; if not, write to the Free Software
      20             : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
      21             : MA 02110-1301, USA.
      22             : 
      23             : */
      24             : 
      25             : #import "AI.h"
      26             : #import "ResourceManager.h"
      27             : #import "OOStringParsing.h"
      28             : #import "OOWeakReference.h"
      29             : #import "OOCacheManager.h"
      30             : #import "OOCollectionExtractors.h"
      31             : #import "OOPListParsing.h"
      32             : 
      33             : #import "ShipEntity.h"
      34             : #import "ShipEntityAI.h"
      35             : 
      36             : 
      37           0 : enum
      38             : {
      39             :         kRecursionLimiter               = 32,   // reactToMethod: recursion
      40             :         kStackLimiter                   = 32    // setAITo: stack overflow
      41             : };
      42             : 
      43             : 
      44           0 : typedef struct
      45             : {
      46           0 :         AI                              *ai;
      47           0 :         SEL                             selector;
      48           0 :         id                              parameter;
      49             : } OOAIDeferredCallTrampolineInfo;
      50             : 
      51             : 
      52           0 : static AI *sCurrentlyRunningAI = nil;
      53             : 
      54             : 
      55             : @interface AI (OOPrivate)
      56             : 
      57             : // Wrapper for performSelector:withObject:afterDelay: to catch/fix bugs.
      58           0 : - (void) performDeferredCall:(SEL)selector withObject:(id)object afterDelay:(NSTimeInterval)delay;
      59           0 : + (void) deferredCallTrampolineWithInfo:(NSValue *)info;
      60             : 
      61           0 : - (void) refreshOwnerDesc;
      62             : 
      63             : // Set state machine and state without side effects.
      64           0 : - (void) directSetStateMachine:(NSDictionary *)newSM name:(NSString *)name;
      65           0 : - (void) directSetState:(NSString *)state;
      66             : 
      67             : // Loading/whitelisting
      68           0 : - (NSDictionary *) loadStateMachine:(NSString *)smName jsName:(NSString *)script;
      69           0 : - (NSDictionary *) cleanHandlers:(NSDictionary *)handlers forState:(NSString *)stateKey stateMachine:(NSString *)smName;
      70           0 : - (NSArray *) cleanActions:(NSArray *)actions forHandler:(NSString *)handlerKey state:(NSString *)stateKey stateMachine:(NSString *)smName;
      71             : 
      72             : @end
      73             : 
      74             : 
      75             : #if DEBUG_GRAPHVIZ
      76             : extern void GenerateGraphVizForAIStateMachine(NSDictionary *stateMachine, NSString *name);
      77             : #endif
      78             : 
      79             : 
      80           0 : @interface OOPreservedAIStateMachine: NSObject
      81             : {
      82             : @private
      83           0 :         NSDictionary            *_stateMachine;
      84           0 :         NSString                        *_name;
      85           0 :         NSString                        *_state;
      86           0 :         NSMutableSet            *_pendingMessages;
      87           0 :         NSString      *_jsScript;
      88             : }
      89             : 
      90           0 : - (id) initWithStateMachine:(NSDictionary *)stateMachine
      91             :                                            name:(NSString *)name
      92             :                                           state:(NSString *)state
      93             :                         pendingMessages:(NSSet *)pendingMessages
      94             :                                                                          jsScript:(NSString *)script;
      95             : 
      96           0 : - (NSDictionary *) stateMachine;
      97           0 : - (NSString *) name;
      98           0 : - (NSString *) state;
      99           0 : - (NSSet *) pendingMessages;
     100           0 : - (NSString *) jsScript;
     101             : 
     102             : @end
     103             : 
     104             : 
     105             : @implementation AI
     106             : 
     107             : + (AI *) currentlyRunningAI
     108             : {
     109             :         return sCurrentlyRunningAI;
     110             : }
     111             : 
     112             : 
     113             : + (NSString *) currentlyRunningAIDescription
     114             : {
     115             :         if (sCurrentlyRunningAI != nil)
     116             :         {
     117             :                 return [NSString stringWithFormat:@"%@ in state %@", [sCurrentlyRunningAI name], [sCurrentlyRunningAI state]];
     118             :         }
     119             :         else
     120             :         {
     121             :                 return @"<no AI running>";
     122             :         }
     123             : }
     124             : 
     125             : 
     126           0 : - (id) init
     127             : {
     128             :         if ((self = [super init]))
     129             :         {
     130             :                 nextThinkTime = INFINITY;       // don't think for a while
     131             :                 thinkTimeInterval = AI_THINK_INTERVAL;
     132             :                 
     133             :                 stateMachineName = @"<no AI>";  // no initial brain
     134             :         }
     135             :         
     136             :         return self;
     137             : }
     138             : 
     139             : 
     140             : - (id) initWithStateMachine:(NSString *)smName andState:(NSString *)stateName
     141             : {
     142             :         if ((self = [self init]))
     143             :         {
     144             :                 if (smName != nil)  [self setStateMachine:smName withJSScript:@"oolite-nullAI.js"];
     145             :                 if (stateName != nil)  currentState = [stateName retain];
     146             :         }
     147             :         
     148             :         return self;
     149             : }
     150             : 
     151             : 
     152           0 : - (void) dealloc
     153             : {
     154             :         if (sCurrentlyRunningAI == self)
     155             :         {
     156             :                 sCurrentlyRunningAI = nil;
     157             :         }
     158             :         
     159             :         DESTROY(_owner);
     160             :         DESTROY(ownerDesc);
     161             :         DESTROY(aiStack);
     162             :         DESTROY(stateMachine);
     163             :         DESTROY(stateMachineName);
     164             :         DESTROY(currentState);
     165             :         DESTROY(pendingMessages);
     166             :         DESTROY(jsScript);
     167             :         
     168             :         [super dealloc];
     169             : }
     170             : 
     171             : 
     172           0 : - (NSString *) descriptionComponents
     173             : {
     174             :         return [NSString stringWithFormat:@"\"%@\" in state: \"%@\" for %@", stateMachineName, currentState, ownerDesc];
     175             : }
     176             : 
     177             : 
     178           0 : - (NSString *) shortDescriptionComponents
     179             : {
     180             :         return [NSString stringWithFormat:@"%@:%@ / %@", stateMachineName, currentState, [stateMachine objectForKey:@"jsScript"]];
     181             : }
     182             : 
     183             : 
     184             : - (ShipEntity *)owner
     185             : {
     186             :         ShipEntity              *owner = [_owner weakRefUnderlyingObject];
     187             :         if (owner == nil)
     188             :         {
     189             :                 [_owner release];
     190             :                 _owner = nil;
     191             :         }
     192             :         
     193             :         return owner;
     194             : }
     195             : 
     196             : 
     197             : - (void) setOwner:(ShipEntity *)ship
     198             : {
     199             :         [_owner release];
     200             :         _owner = [ship weakRetain];
     201             :         [self refreshOwnerDesc];
     202             : }
     203             : 
     204             : 
     205           0 : - (void) reportStackOverflow
     206             : {
     207             :         if (OOLogWillDisplayMessagesInClass(@"ai.error.stackOverflow"))
     208             :         {
     209             :                 BOOL stackDump = OOLogWillDisplayMessagesInClass(@"ai.error.stackOverflow.dump");
     210             :                 
     211             :                 NSString *trailer = stackDump ? @" -- stack:" : @".";
     212             :                 OOLogERR(@"ai.error.stackOverflow", @"AI stack overflow for %@ in %@: %@%@\n", [_owner shortDescription], stateMachineName, currentState, trailer);
     213             :                 
     214             :                 if (stackDump)
     215             :                 {
     216             :                         OOLogIndent();
     217             :                         
     218             :                         NSUInteger count = [aiStack count];
     219             :                         while (count--)
     220             :                         {
     221             :                                 OOPreservedAIStateMachine *preservedMachine = [aiStack objectAtIndex:count];
     222             :                                 OOLog(@"ai.error.stackOverflow.dump", @"%3lu: %@: %@", count, [preservedMachine name], [preservedMachine state]);
     223             :                         }
     224             :                         
     225             :                         OOLogOutdent();
     226             :                 }
     227             :         }
     228             : }
     229             : 
     230             : 
     231             : - (void) preserveCurrentStateMachine
     232             : {
     233             :         if (stateMachine == nil)  return;
     234             :         
     235             :         if (aiStack == nil)
     236             :         {
     237             :                 aiStack = [[NSMutableArray alloc] init];
     238             :         }
     239             :         
     240             :         if ([aiStack count] >= kStackLimiter)
     241             :         {
     242             :                 [self reportStackOverflow];
     243             :                 
     244             :                 [NSException raise:@"OoliteException"
     245             :                                         format:@"AI stack overflow for %@", _owner];
     246             :         }
     247             :         
     248             :         OOPreservedAIStateMachine *preservedMachine = [[OOPreservedAIStateMachine alloc]
     249             :                                                                                                    initWithStateMachine:stateMachine
     250             :                                                                                                                                    name:stateMachineName
     251             :                                                                                                                                   state:currentState
     252             :                                                                                                                 pendingMessages:pendingMessages
     253             :                                                                                                                                                                                                         jsScript:[stateMachine objectForKey:@"jsScript"]];
     254             :         
     255             : #ifndef NDEBUG
     256             :         if ([[self owner] reportAIMessages])  OOLog(@"ai.stack.push", @"Pushing state machine for %@", self);
     257             : #endif
     258             :         
     259             :         [aiStack addObject:preservedMachine];  // PUSH
     260             :         
     261             :         [preservedMachine release];
     262             : }
     263             : 
     264             : 
     265             : - (void) restorePreviousStateMachine
     266             : {
     267             :         if ([aiStack count] == 0)  return;
     268             :         
     269             :         OOPreservedAIStateMachine *preservedMachine = [aiStack lastObject];
     270             :         
     271             : #ifndef NDEBUG
     272             :         if ([[self owner] reportAIMessages])  OOLog(@"ai.stack.pop", @"Popping previous state machine for %@", self);
     273             : #endif
     274             :         
     275             :         [self directSetStateMachine:[preservedMachine stateMachine]
     276             :                                                    name:[preservedMachine name]];
     277             :         
     278             :         [self directSetState:[preservedMachine state]];
     279             :         
     280             :         // restore JS script
     281             :         [[self owner] setAIScript:[preservedMachine jsScript]];
     282             : 
     283             :         [pendingMessages release];
     284             :         pendingMessages = [[preservedMachine pendingMessages] mutableCopy];  // restore a MUTABLE set
     285             :         
     286             :         [aiStack removeLastObject];  //  POP
     287             : }
     288             : 
     289             : 
     290             : - (BOOL) hasSuspendedStateMachines
     291             : {
     292             :         return [aiStack count] != 0;
     293             : }
     294             : 
     295             : 
     296             : - (void) exitStateMachineWithMessage:(NSString *)message
     297             : {
     298             :         if ([aiStack count] != 0)
     299             :         {
     300             :                 [self restorePreviousStateMachine];
     301             :                 if (message == nil)  message = @"RESTARTED";
     302             :                 [self reactToMessage:message context:@"suspended AI restart"];
     303             :         }
     304             : }
     305             : 
     306             : 
     307             : - (void) setStateMachine:(NSString *)smName withJSScript:(NSString *)script
     308             : {
     309             :         NSDictionary *newSM = [self loadStateMachine:smName jsName:script];
     310             : 
     311             :         if (newSM)
     312             :         {
     313             :                 [self preserveCurrentStateMachine];
     314             :                 [self directSetStateMachine:newSM name:smName];
     315             :                 [self directSetState:@"GLOBAL"];
     316             :                 
     317             :                 nextThinkTime = 0.0;    // think at next tick
     318             : 
     319             :                 /*      CRASH in objc_msgSend, apparently on [self reactToMessage:@"ENTER"] (1.69, OS X/x86).
     320             :                         Analysis: self corrupted. We're being called by __NSFireDelayedPerform, which doesn't go
     321             :                         through -[NSObject performSelector:withObject:], suggesting it's using IMP caching. An
     322             :                         invalid self is therefore possible.
     323             :                         Attempted fix: new delayed dispatch with trampoline, see -[AI setStateMachine:afterDelay:].
     324             :                          -- Ahruman, 20070706
     325             :                 */
     326             :                 [self reactToMessage:@"ENTER" context:@"changing AI"];
     327             :                 
     328             :                 // refresh name
     329             :                 [self refreshOwnerDesc];
     330             :         }
     331             : }
     332             : 
     333             : 
     334             : - (void) setState:(NSString *) stateName
     335             : {
     336             :         if ([stateMachine objectForKey:stateName])
     337             :         {
     338             :                 /*      CRASH in objc_msgSend, apparently on [self reactToMessage:@"EXIT"] (1.69, OS X/x86).
     339             :                         Analysis: self corrupted. We're being called by __NSFireDelayedPerform, which doesn't go
     340             :                         through -[NSObject performSelector:withObject:], suggesting it's using IMP caching. An
     341             :                         invalid self is therefore possible.
     342             :                         Attempted fix: new delayed dispatch with trampoline, see -[AI setState:afterDelay:].
     343             :                          -- Ahruman, 20070706
     344             :                 */
     345             :                 [self reactToMessage:@"EXIT" context:@"changing state"];
     346             :                 [self directSetState:stateName];
     347             :                 [self reactToMessage:@"ENTER" context:@"changing state"];
     348             :         }
     349             : }
     350             : 
     351             : 
     352             : - (void) setStateMachine:(NSString *)smName afterDelay:(NSTimeInterval)delay
     353             : {
     354             :         [self performDeferredCall:@selector(setStateMachine:) withObject:smName afterDelay:delay];
     355             : }
     356             : 
     357             : 
     358             : - (void) setState:(NSString *)stateName afterDelay:(NSTimeInterval)delay
     359             : {
     360             :         [self performDeferredCall:@selector(setState:) withObject:stateName afterDelay:delay];
     361             : }
     362             : 
     363             : 
     364             : - (NSString *) name
     365             : {
     366             :         return [[stateMachineName retain] autorelease];
     367             : }
     368             : 
     369             : 
     370             : - (NSString *) associatedJS
     371             : {
     372             :         return [stateMachine objectForKey:@"jsScript"];
     373             : }
     374             : 
     375             : 
     376             : - (NSString *) state
     377             : {
     378             :         return [[currentState retain] autorelease];
     379             : }
     380             : 
     381             : 
     382             : - (NSUInteger) stackDepth
     383             : {
     384             :         return [aiStack count];
     385             : }
     386             : 
     387             : 
     388             : #ifndef NDEBUG
     389           0 : typedef struct AIStackElement AIStackElement;
     390           0 : struct AIStackElement
     391             : {
     392           0 :         AIStackElement                  *back;
     393           0 :         ShipEntity                              *owner;
     394           0 :         NSString                                *aiName;
     395           0 :         NSString                                *state;
     396           0 :         NSString                                *message;
     397           0 :         NSString                                *context;
     398             : };
     399             : 
     400           0 : static AIStackElement *sStack = NULL;
     401             : #endif
     402             : 
     403             : 
     404             : - (void) reactToMessage:(NSString *) message context:(NSString *)debugContext
     405             : {
     406             :         unsigned                i;
     407             :         NSArray                 *actions = nil;
     408             :         NSDictionary    *messagesForState = nil;
     409             :         ShipEntity              *owner = [self owner];
     410             :         static unsigned recursionLimiter = 0;
     411             :         AI                              *previousRunning = sCurrentlyRunningAI;
     412             :         
     413             :         /*      CRASH in _freedHandler when called via -setState: __NSFireDelayedPerform (1.69, OS X/x86).
     414             :                 Analysis: owner invalid.
     415             :                 Fix: make owner an OOWeakReference.
     416             :                  -- Ahruman, 20070706
     417             :         */
     418             :         if (message == nil || owner == nil || [owner universalID] == NO_TARGET)  return;
     419             : 
     420             : #ifndef NDEBUG
     421             :         // Push debug stack frame.
     422             :         if (debugContext == nil)  debugContext = @"unspecified";
     423             :         AIStackElement stackElement =
     424             :         {
     425             :                 .back = sStack,
     426             :                 .owner = owner,
     427             :                 .aiName = [[stateMachineName retain] autorelease],
     428             :                 .state = [[currentState retain] autorelease],
     429             :                 .message = message,
     430             :                 .context = debugContext
     431             :         };
     432             :         sStack = &stackElement;
     433             : #endif
     434             :         
     435             :         /*      CRASH when calling reactToMessage: FOO in state FOO causes infinite
     436             :                 recursion. (NB: there are other ways of triggering this.)
     437             :                 FIX: recursion limiter. Alternative is to explicitly catch this case
     438             :                 in takeAction:, but that could potentially miss indirect recursion via
     439             :                 scripts.
     440             :         */
     441             :         if (recursionLimiter > kRecursionLimiter)
     442             :         {
     443             :                 OOLogERR(@"ai.error.recursion", @"AI dispatch: hit stack depth limit in AI %@, state %@ handling message %@ in context \"%@\", aborting.", stateMachineName, currentState, message, debugContext);
     444             :                 
     445             : #ifndef NDEBUG
     446             :                 AIStackElement *stack = sStack;
     447             :                 unsigned depth = 0;
     448             :                 while (stack != NULL)
     449             :                 {
     450             :                         OOLog(@"ai.error.recursion.stackTrace", @"%4u  %@ - %@:%@.%@ (%@)", depth++, [stack->owner shortDescription], stack->aiName, stack->state, stack->message, stack->context);
     451             :                         stack = stack->back;
     452             :                 }
     453             :                 
     454             :                 // unwind.
     455             :                 if (sStack != NULL)  sStack = sStack->back;
     456             : #endif
     457             :                 
     458             :                 return;
     459             :         }
     460             :         
     461             :         messagesForState = [stateMachine objectForKey:currentState];
     462             :         if (messagesForState == nil)  return;
     463             :         
     464             : #ifndef NDEBUG
     465             :         if (currentState != nil && ![message isEqual:@"UPDATE"] && [owner reportAIMessages])
     466             :         {
     467             :                 OOLog(@"ai.message.receive", @"AI %@ for %@ in state '%@' receives message '%@'. Context: %@, stack depth: %u", stateMachineName, ownerDesc, currentState, message, debugContext, recursionLimiter);
     468             :         }
     469             : #endif
     470             :         
     471             :         actions = [[[messagesForState objectForKey:message] copy] autorelease];
     472             :         
     473             :         sCurrentlyRunningAI = self;
     474             :         if ([actions count] > 0)
     475             :         {
     476             :                 ++recursionLimiter;
     477             :                 @try
     478             :                 {
     479             :                         for (i = 0; i < [actions count]; i++)
     480             :                         {
     481             :                                 [self takeAction:[actions objectAtIndex:i]];
     482             :                         }
     483             :                 }
     484             :                 @catch (NSException *exception)
     485             :                 {
     486             :                         OOLog(kOOLogException, @"Squashing exception %@:%@ in AI handler %@:%@.%@", [exception name], [exception reason], stateMachineName, currentState, message);
     487             :                 }
     488             :                 
     489             :                 --recursionLimiter;
     490             :         }
     491             :         else
     492             :         {
     493             :                 if (currentState != nil)
     494             :                 {
     495             :                         if ([owner respondsToSelector:@selector(interpretAIMessage:)])
     496             :                         {
     497             :                                 [owner performSelector:@selector(interpretAIMessage:) withObject:message];
     498             :                         }
     499             :                 }
     500             :         }
     501             :         
     502             :         sCurrentlyRunningAI = previousRunning;
     503             : #ifndef NDEBUG
     504             :         // Unwind stack.
     505             :         if (sStack != NULL)  sStack = sStack->back;
     506             : #endif
     507             : }
     508             : 
     509             : 
     510             : - (void) takeAction:(NSString *)action
     511             : {
     512             :         ShipEntity *owner = [self owner];
     513             :         
     514             : #ifndef NDEBUG
     515             :         BOOL report = [owner reportAIMessages];
     516             :         if (report)
     517             :         {
     518             :                 OOLog(@"ai.takeAction", @"%@ to take action %@", ownerDesc, action);
     519             :                 OOLogIndent();
     520             :         }
     521             : #endif
     522             :         
     523             :         NSArray *tokens = ScanTokensFromString(action);
     524             :         NSUInteger tokenCount = [tokens count];
     525             :         
     526             :         if (tokenCount != 0)
     527             :         {
     528             :                 NSString *selectorStr = [tokens objectAtIndex:0];
     529             :                 
     530             :                 if (owner != nil)
     531             :                 {
     532             :                         NSString *dataString = nil;
     533             :                         
     534             :                         if (tokenCount == 2)
     535             :                         {
     536             :                                 dataString = [tokens objectAtIndex:1];
     537             :                         }
     538             :                         else if ([tokens count] > 1)
     539             :                         {
     540             :                                 dataString = [[tokens subarrayWithRange:NSMakeRange(1, tokenCount - 1)] componentsJoinedByString:@" "];
     541             :                         }
     542             :                         
     543             :                         SEL selector = NSSelectorFromString(selectorStr);
     544             :                         if ([owner respondsToSelector:selector])
     545             :                         {
     546             :                                 if (dataString != nil)  [owner performSelector:selector withObject:dataString];
     547             :                                 else  [owner performSelector:selector];
     548             :                         }
     549             :                         else
     550             :                         {
     551             :                                 OOLogERR(@"ai.takeAction.badSelector", @"in AI %@ in state %@: %@ does not respond to %@", stateMachineName, currentState, ownerDesc, selectorStr);
     552             :                         }
     553             :                 }
     554             :                 else
     555             :                 {
     556             :                         OOLog(@"ai.takeAction.orphaned", @"***** AI %@, trying to perform %@, is orphaned (no owner)", stateMachineName, selectorStr);
     557             :                 }
     558             :         }
     559             :         else
     560             :         {
     561             : #ifndef NDEBUG
     562             :                 if (report)  OOLog(@"ai.takeAction.noAction", @"DEBUG: - no action '%@'", action);
     563             : #endif
     564             :         }
     565             :         
     566             : #ifndef NDEBUG
     567             :         if (report)
     568             :         {
     569             :                 OOLogOutdent();
     570             :         }
     571             : #endif
     572             : }
     573             : 
     574             : 
     575             : - (void) think
     576             : {
     577             :         NSArray                 *ms_list = nil;
     578             :         unsigned                i;
     579             :         
     580             :         if ([[self owner] universalID] == NO_TARGET || stateMachine == nil)  return;  // don't think until launched
     581             :         
     582             :         [self reactToMessage:@"UPDATE" context:@"periodic update"];
     583             : 
     584             :         if ([pendingMessages count] > 0)
     585             :         {
     586             :                 ms_list = [pendingMessages allObjects];
     587             :                 [pendingMessages removeAllObjects];
     588             :         }
     589             :         
     590             :         if (ms_list != nil)
     591             :         {
     592             :                 for (i = 0; i < [ms_list count]; i++)
     593             :                 {
     594             :                         [self reactToMessage:[ms_list objectAtIndex:i] context:@"handling deferred message"];
     595             :                 }
     596             :         }
     597             : }
     598             : 
     599             : 
     600             : - (void) message:(NSString *)ms
     601             : {
     602             :         if ([[self owner] universalID] == NO_TARGET)  return;  // don't think until launched
     603             : 
     604             :         if (EXPECT_NOT([pendingMessages count] > 32))
     605             :         {
     606             :                 // Generate the error, but don't crash Oolite! Fixes bug #18055 - Pending message overflow for thargoids, -> crash !
     607             :                 OOLogERR(@"ai.message.failed.overflow", @"AI message \"%@\" received by '%@' AI while pending messages stack full; message discarded. Pending messages:\n%@", ms, ownerDesc, pendingMessages);
     608             :         }
     609             :         else
     610             :         {
     611             :                 if (pendingMessages == nil)
     612             :                 {
     613             :                         pendingMessages = [[NSMutableSet alloc] init];
     614             :                 }
     615             :                 [pendingMessages addObject:ms];
     616             :         }
     617             : }
     618             : 
     619             : 
     620             : - (void) dropMessage:(NSString *)ms
     621             : {
     622             :         [pendingMessages removeObject:ms];
     623             : }
     624             :         
     625             : 
     626             : - (NSSet *) pendingMessages
     627             : {
     628             :         if (pendingMessages != nil)
     629             :         {
     630             :                 return [[pendingMessages copy] autorelease];
     631             :         }
     632             :         else
     633             :         {
     634             :                 return [NSSet set];
     635             :         }
     636             : }
     637             : 
     638             : 
     639             : - (void) debugDumpPendingMessages
     640             : {
     641             :         NSArray                         *sortedMessages = nil;
     642             :         NSString                        *displayMessages = nil;
     643             :         
     644             :         if ([pendingMessages count] > 0)
     645             :         {
     646             :                 sortedMessages = [[pendingMessages allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
     647             :                 displayMessages = [sortedMessages componentsJoinedByString:@", "];
     648             :         }
     649             :         else
     650             :         {
     651             :                 displayMessages = @"none";
     652             :         }
     653             :         
     654             :         OOLog(@"ai.debug.pendingMessages", @"Pending messages for AI %@: %@", [self descriptionComponents], displayMessages);
     655             : }
     656             : 
     657             : 
     658             : - (void) setNextThinkTime:(OOTimeAbsolute) ntt
     659             : {
     660             :         nextThinkTime = ntt;
     661             : }
     662             : 
     663             : 
     664             : - (OOTimeAbsolute) nextThinkTime
     665             : {
     666             :         if (!stateMachine)
     667             :                 return INFINITY;
     668             : 
     669             :         return nextThinkTime;
     670             : }
     671             : 
     672             : 
     673             : - (void) setThinkTimeInterval:(OOTimeDelta) tti
     674             : {
     675             :         thinkTimeInterval = tti;
     676             : }
     677             : 
     678             : 
     679             : - (OOTimeDelta) thinkTimeInterval
     680             : {
     681             :         return thinkTimeInterval;
     682             : }
     683             : 
     684             : 
     685             : - (void) clearStack
     686             : {
     687             :         [aiStack removeAllObjects];
     688             : }
     689             : 
     690             : 
     691             : - (void) clearAllData
     692             : {
     693             :         [aiStack removeAllObjects];
     694             :         [pendingMessages removeAllObjects];
     695             :         
     696             :         nextThinkTime += 36000.0;       // should dealloc in under ten hours!
     697             : }
     698             : 
     699             : 
     700             : - (void)dumpState
     701             : {
     702             :         OOLog(@"dumpState.ai", @"State machine name: %@", stateMachineName);
     703             :         OOLog(@"dumpState.ai", @"Current state: %@", currentState);
     704             :         OOLog(@"dumpState.ai", @"Next think time: %g", nextThinkTime);
     705             :         OOLog(@"dumpState.ai", @"Next think interval: %g", thinkTimeInterval);
     706             : }
     707             : 
     708             : @end
     709             : 
     710             : 
     711             : /*      This is an attempt to fix the bugs referred to above regarding calls from
     712             :         __NSFireDelayedPerform with a corrupt self. I'm not certain whether this
     713             :         will fix the issue or merely cause a less weird crash in
     714             :         +deferredCallTrampolineWithInfo:.
     715             :         -- Ahruman 20070706
     716             : */
     717             : @implementation AI (OOPrivate)
     718             : 
     719             : - (void)performDeferredCall:(SEL)selector withObject:(id)object afterDelay:(NSTimeInterval)delay
     720             : {
     721             :         OOAIDeferredCallTrampolineInfo  infoStruct;
     722             :         NSValue                                                 *info = nil;
     723             :         
     724             :         if (selector != NULL)
     725             :         {
     726             :                 infoStruct.ai = [self retain];
     727             :                 infoStruct.selector = selector;
     728             :                 infoStruct.parameter = object;
     729             :                 
     730             :                 info = [[NSValue alloc] initWithBytes:&infoStruct objCType:@encode(OOAIDeferredCallTrampolineInfo)];
     731             :                 
     732             :                 [[AI class] performSelector:@selector(deferredCallTrampolineWithInfo:)
     733             :                                                  withObject:info
     734             :                                                  afterDelay:delay];
     735             :                 [info release];
     736             :         }
     737             : }
     738             : 
     739             : 
     740             : + (void)deferredCallTrampolineWithInfo:(NSValue *)info
     741             : {
     742             :         OOAIDeferredCallTrampolineInfo  infoStruct;
     743             :         
     744             :         if (info != nil)
     745             :         {
     746             :                 assert(strcmp([info objCType], @encode(OOAIDeferredCallTrampolineInfo)) == 0);
     747             :                 [info getValue:&infoStruct];
     748             :                 
     749             :                 [infoStruct.ai performSelector:infoStruct.selector withObject:infoStruct.parameter];
     750             :                 
     751             :                 [infoStruct.ai release];
     752             :                 [infoStruct.parameter release];
     753             :         }
     754             : }
     755             : 
     756             : 
     757             : - (void)refreshOwnerDesc
     758             : {
     759             :         ShipEntity *owner = [self owner];
     760             :         [ownerDesc release];
     761             :         if ([owner isPlayer])
     762             :         {
     763             :                 ownerDesc = @"player autopilot";
     764             :         }
     765             :         else if (owner != nil)
     766             :         {
     767             :                 ownerDesc = [[NSString alloc] initWithFormat:@"%@ %d", [owner name], [owner universalID]];
     768             :         }
     769             :         else
     770             :         {
     771             :                 ownerDesc = @"no owner";
     772             :         }
     773             : }
     774             : 
     775             : 
     776             : - (void) directSetStateMachine:(NSDictionary *)newSM name:(NSString *)name
     777             : {
     778             :         if (stateMachine != newSM)
     779             :         {
     780             :                 [stateMachine release];
     781             :                 stateMachine = [newSM copy];
     782             :         }
     783             :         if (stateMachineName != name)
     784             :         {
     785             :                 [stateMachineName release];
     786             :                 stateMachineName = [name copy];
     787             :         }
     788             : }
     789             : 
     790             : 
     791             : - (void) directSetState:(NSString *)state
     792             : {
     793             :         if (currentState != state)
     794             :         {
     795             :                 [currentState release];
     796             :                 currentState = [state copy];
     797             :         }
     798             : }
     799             : 
     800             : 
     801             : - (NSDictionary *) loadStateMachine:(NSString *)smName jsName:(NSString *)script
     802             : {
     803             :         NSDictionary                    *newSM = nil;
     804             :         NSMutableDictionary             *cleanSM = nil;
     805             :         OOCacheManager                  *cacheMgr = [OOCacheManager sharedCache];
     806             :         NSEnumerator                    *stateEnum = nil;
     807             :         NSString                                *stateKey = nil;
     808             :         NSDictionary                    *stateHandlers = nil;
     809             :         NSAutoreleasePool               *pool = nil;
     810             :         
     811             :         if (![smName isEqualToString:@"nullAI.plist"])
     812             :         {
     813             :                 // don't cache nullAI since they're different depending on associated JS AI
     814             :                 newSM = [cacheMgr objectForKey:smName inCache:@"AIs"];
     815             :                 if (newSM != nil && ![newSM isKindOfClass:[NSDictionary class]])  return nil;   // catches use of @"nil" to indicate no AI found.
     816             :         }
     817             :         
     818             :         if (newSM == nil)
     819             :         {
     820             :                 pool = [[NSAutoreleasePool alloc] init];
     821             :                 OOLog(@"ai.load", @"Loading and sanitizing AI \"%@\"", smName);
     822             :                 OOLogPushIndent();
     823             :                 OOLogIndentIf(@"ai.load");
     824             :                 
     825             :                 @try
     826             :                 {
     827             :                         // Load state machine and validate against whitelist.
     828             :                         NSString *aiPath = [ResourceManager pathForFileNamed:smName inFolder:@"AIs"];
     829             :                         if (aiPath != nil)
     830             :                         {
     831             :                                 newSM = OODictionaryFromFile(aiPath);
     832             :                         }
     833             :                         if (newSM == nil)
     834             :                         {
     835             :                                 [cacheMgr setObject:@"nil" forKey:smName inCache:@"AIs"];
     836             :                                 NSString *fromString = @"";
     837             :                                 if ([self state] != nil)
     838             :                                 {
     839             :                                         fromString = [NSString stringWithFormat:@" from %@:%@", [self name], [self state]];
     840             :                                 }
     841             :                                 OOLog(@"ai.load.failed.unknownAI", @"Can't switch AI for %@%@ to \"%@\" - could not load file.", [[self owner] shortDescription], fromString, smName);
     842             :                                 return nil;
     843             :                         }
     844             :                         
     845             :                         cleanSM = [NSMutableDictionary dictionaryWithCapacity:[newSM count]];
     846             :                         
     847             :                         for (stateEnum = [newSM keyEnumerator]; (stateKey = [stateEnum nextObject]); )
     848             :                         {
     849             :                                 stateHandlers = [newSM objectForKey:stateKey];
     850             :                                 if (![stateHandlers isKindOfClass:[NSDictionary class]])
     851             :                                 {
     852             :                                         OOLogWARN(@"ai.invalidFormat.state", @"State \"%@\" in AI \"%@\" is not a dictionary, ignoring.", stateKey, smName);
     853             :                                         continue;
     854             :                                 }
     855             :                                 
     856             :                                 stateHandlers = [self cleanHandlers:stateHandlers forState:stateKey stateMachine:smName];
     857             :                                 [cleanSM setObject:stateHandlers forKey:stateKey];
     858             :                         }
     859             :                         [cleanSM setObject:script forKey:@"jsScript"];
     860             : 
     861             :                         // Make immutable.
     862             :                         newSM = [[cleanSM copy] autorelease];
     863             :                         
     864             : #if DEBUG_GRAPHVIZ
     865             :                         if ([[NSUserDefaults standardUserDefaults] boolForKey:@"generate-ai-graphviz"])
     866             :                         {
     867             :                                 GenerateGraphVizForAIStateMachine(newSM, smName);
     868             :                         }
     869             : #endif
     870             :                         
     871             :                         // Cache.
     872             :                         [cacheMgr setObject:newSM forKey:smName inCache:@"AIs"];
     873             :                 }
     874             :                 @finally
     875             :                 {
     876             :                         OOLogPopIndent();
     877             :                 }
     878             :                 
     879             :                 [newSM retain];
     880             :                 [pool release];
     881             :                 [newSM autorelease];
     882             :         }
     883             :         
     884             :         return newSM;
     885             : }
     886             : 
     887             : 
     888             : - (NSDictionary *) cleanHandlers:(NSDictionary *)handlers forState:(NSString *)stateKey stateMachine:(NSString *)smName
     889             : {
     890             :         NSEnumerator                    *handlerEnum = nil;
     891             :         NSString                                *handlerKey = nil;
     892             :         NSArray                                 *handlerActions = nil;
     893             :         NSMutableDictionary             *result = nil;
     894             :         
     895             :         result = [NSMutableDictionary dictionaryWithCapacity:[handlers count]];
     896             :         for (handlerEnum = [handlers keyEnumerator]; (handlerKey = [handlerEnum nextObject]); )
     897             :         {
     898             :                 handlerActions = [handlers objectForKey:handlerKey];
     899             :                 if (![handlerActions isKindOfClass:[NSArray class]])
     900             :                 {
     901             :                         OOLogWARN(@"ai.invalidFormat.handler", @"Handler \"%@\" for state \"%@\" in AI \"%@\" is not an array, ignoring.", handlerKey, stateKey, smName);
     902             :                         continue;
     903             :                 }
     904             :                 
     905             :                 handlerActions = [self cleanActions:handlerActions forHandler:handlerKey state:stateKey stateMachine:smName];
     906             :                 [result setObject:handlerActions forKey:handlerKey];
     907             :         }
     908             :         
     909             :         // Return immutable copy.
     910             :         return [[result copy] autorelease];
     911             : }
     912             : 
     913             : 
     914             : - (NSArray *) cleanActions:(NSArray *)actions forHandler:(NSString *)handlerKey state:(NSString *)stateKey stateMachine:(NSString *)smName
     915             : {
     916             :         NSEnumerator                    *actionEnum = nil;
     917             :         NSString                                *action = nil;
     918             :         NSRange                                 spaceRange;
     919             :         NSString                                *selector = nil;
     920             :         id                                              aliasedSelector = nil;
     921             :         NSMutableArray                  *result = nil;
     922             :         static NSSet                    *whitelist = nil;
     923             :         static NSDictionary             *aliases = nil;
     924             :         NSArray                                 *whitelistArray1 = nil;
     925             :         NSArray                                 *whitelistArray2 = nil;
     926             :         
     927             :         if (whitelist == nil)
     928             :         {
     929             :                 whitelistArray1 = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_methods"];
     930             :                 if (whitelistArray1 == nil)  whitelistArray1 = [NSArray array];
     931             :                 whitelistArray2 = [[ResourceManager whitelistDictionary] oo_arrayForKey:@"ai_and_action_methods"];
     932             :                 if (whitelistArray2 != nil)  whitelistArray1 = [whitelistArray1 arrayByAddingObjectsFromArray:whitelistArray2];
     933             :                 
     934             :                 whitelist = [[NSSet alloc] initWithArray:whitelistArray1];
     935             :                 aliases = [[[ResourceManager whitelistDictionary] oo_dictionaryForKey:@"ai_method_aliases"] retain];
     936             :         }
     937             :         
     938             :         result = [NSMutableArray arrayWithCapacity:[actions count]];
     939             :         for (actionEnum = [actions objectEnumerator]; (action = [actionEnum nextObject]); )
     940             :         {
     941             :                 if (![action isKindOfClass:[NSString class]])
     942             :                 {
     943             :                         OOLogWARN(@"ai.invalidFormat.action", @"An action in handler \"%@\" for state \"%@\" in AI \"%@\" is not a string, ignoring.", handlerKey, stateKey, smName);
     944             :                         continue;
     945             :                 }
     946             :                 
     947             :                 // Trim spaces from beginning and end.
     948             :                 action = [action stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
     949             :                 
     950             :                 // Cut off parameters.
     951             :                 spaceRange = [action rangeOfString:@" "];
     952             :                 if (spaceRange.location == NSNotFound)  selector = action;
     953             :                 else  selector = [action substringToIndex:spaceRange.location];
     954             :                 
     955             :                 // Look in alias table.
     956             :                 aliasedSelector = [aliases objectForKey:selector];
     957             :                 if (aliasedSelector != nil)
     958             :                 {
     959             :                         if ([aliasedSelector isKindOfClass:[NSString class]])
     960             :                         {
     961             :                                 // Change selector and action to use real method name.
     962             :                                 selector = aliasedSelector;
     963             :                                 if (spaceRange.location == NSNotFound)  action = aliasedSelector;
     964             :                                 else action = [aliasedSelector stringByAppendingString:[action substringFromIndex:spaceRange.location]];
     965             :                         }
     966             :                         else if ([aliasedSelector isKindOfClass:[NSArray class]] && [aliasedSelector count] != 0)
     967             :                         {
     968             :                                 // Alias is complete expression, pretokenized in anticipation of a tokenized future.
     969             :                                 action = [aliasedSelector componentsJoinedByString:@" "];
     970             :                                 selector = [[aliasedSelector objectAtIndex:0] description];
     971             :                         }
     972             :                 }
     973             :                 
     974             :                 // Check for selector in whitelist.
     975             :                 if (![whitelist containsObject:selector])
     976             :                 {
     977             :                         OOLog(@"ai.unpermittedMethod", @"Handler \"%@\" for state \"%@\" in AI \"%@\" uses \"%@\", which is not a permitted AI method.", handlerKey, stateKey, smName, selector);
     978             :                         continue;
     979             :                 }
     980             :                 
     981             :                 [result addObject:action];
     982             :         }
     983             :         
     984             :         // Return immutable copy.
     985             :         return [[result copy] autorelease];
     986             : }
     987             : 
     988             : @end
     989             : 
     990             : 
     991             : @implementation OOPreservedAIStateMachine
     992             : 
     993             : - (id) initWithStateMachine:(NSDictionary *)stateMachine
     994             :                                            name:(NSString *)name
     995             :                                           state:(NSString *)state
     996             :                         pendingMessages:(NSSet *)pendingMessages
     997             :                                                                          jsScript:(NSString *)script
     998             : {
     999             :         if ((self = [super init]))
    1000             :         {
    1001             :                 _stateMachine = [stateMachine copy];
    1002             :                 _name = [name copy];
    1003             :                 _state = [state copy];
    1004             :                 _pendingMessages = [pendingMessages copy];
    1005             :                 _jsScript = [script copy];
    1006             :         }
    1007             :         
    1008             :         return self;
    1009             : }
    1010             : 
    1011             : 
    1012           0 : - (void) dealloc
    1013             : {
    1014             :         [_stateMachine autorelease];
    1015             :         [_name autorelease];
    1016             :         [_state autorelease];
    1017             :         [_pendingMessages autorelease];
    1018             :         [_jsScript autorelease];
    1019             :         
    1020             :         [super dealloc];
    1021             : }
    1022             : 
    1023             : 
    1024             : - (NSDictionary *) stateMachine
    1025             : {
    1026             :         return _stateMachine;
    1027             : }
    1028             : 
    1029             : 
    1030             : - (NSString *) name
    1031             : {
    1032             :         return _name;
    1033             : }
    1034             : 
    1035             : 
    1036             : - (NSString *) state
    1037             : {
    1038             :         return _state;
    1039             : }
    1040             : 
    1041             : 
    1042             : - (NSSet *) pendingMessages
    1043             : {
    1044             :         return _pendingMessages;
    1045             : }
    1046             : 
    1047             : - (NSString *) jsScript
    1048             : {
    1049             :         return _jsScript;
    1050             : }
    1051             : 
    1052             : @end

Generated by: LCOV version 1.14