JavaScript EditorFree JavaScript Editor     Ajax Editor 



Main Page
  Previous Section Next Section

Implementation

Having defined a formal interface, the implementation of the RBS is abstracted out. By reusing containers from the standard template library to store both the rulebase and the working memory, the reference implementation remains simple, too. Using a flexible file format (as described in the preceding section) means most of the preparations can be left to the developer. Notably, thanks to the rule priorities, the interpreter can perform a simple traversal of the rules—as suggested by the knowledge engineer.

All the implementation is transparent from the interface, so extending it would usually not break the system. If we want to optimize the rules, for example, we can build a tree internally after the rules are loaded (although this would involve discarding the priorities).

Source Code

The C++ module that implements the RBS is available on the web site at http://AiGameDev.com/. There's also a high-level walkthrough of the implementation available, and the source code has extensive comments on the lower-level details.


Data Structures

One of the essential concepts for maintaining an efficient implementation is to represent the symbols as integers rather than strings. Because all the interfaces deal with strings (for intuitiveness during initialization), we need an internal map to convert the strings to integer symbols.

After this is done, the working memory can be a simple array, with the integer symbols used directly as the index in the working memory. The effectors and sensors are stored in two containers associating integer symbols with native functions passed by the interface.

Interpreter

The main loop of the interpreter is split into three steps conceptually. First, CheckSensors() calls the native functions to set the appropriate symbols. Doing this before the matching phase guarantees the function is only called once. However, we could sacrifice a small amount of memory and opt for lazy evaluation instead (for instance, a "gathered" bit). The native functions are checked as they are needed by the rules, only if the symbol value is not up-to-date.

Then, ApplyRules() scans through each of the rules in the order they were specified. Each condition in the rule head is checked. If one condition doesn't match, the rest are skipped and the next rule is used. If all the conditions match, the default values are applied. Only then is the rule body executed to override the defaults and set other symbols as appropriate. If no rules match, the defaults are set.

Finally, CheckEffectors() scans only the effector symbols in the working memory and calls the native functions if any of them are true. Because we're using the RBS for control, it's more appropriate for the effectors to be checked every iteration—rather than when a rule is executed. This reduces the number of rules required.

      Previous Section Next Section
    



    JavaScript EditorAjax Editor     JavaScript Editor