![]() | ![]() | ![]() | Creating a GBBopen Application | ![]() |
GBBopen's Module Manager Facility provides mechanisms that make it easy to define and use your own GBBopen applications.
tutorial-example.lisp
(in-package :gbbopen-user) (define-unit-class location () (time x y (next-location :link (location previous-location :singular t) :singular t) (previous-location :link (location next-location :singular t) :singular t)) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) ;;; ==================================================================== ;;; Startup KS (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0): (make-instance 'location :time 0 :x 0 :y 0)) (define-ks startup-ks :trigger-events ((control-shell-started-event)) :execution-function 'startup-ks-function) ;;; ==================================================================== ;;; Initializations (run at Agenda Shell startup) (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :dimensions (dimensions-of 'location))) (add-event-function 'initializations 'control-shell-started-event ;; Initializations should be done first! :priority 100) ;;; ==================================================================== ;;; Random-walk KS (defun add-linear-variance (value max-variance) ;;; Returns a new random value in the interval ;;; [(- value max-variance), (+ value max-variance)] (+ value (- (random (1+ (* max-variance 2))) max-variance))) (defun random-walk-ks-function (ksa) ;;; Move to the next (random) location in the world (let* ((trigger-instance (sole-trigger-instance-of ksa)) ;; The new time is one greater than the stimulus's time: (time (1+ (time-of trigger-instance)))) (cond ;; If the maximum time value (75) is reached, tell the user we've ;; walked too long: ((>= time 75) (format t "~2&Walked too long.~%")) (t ;; The new location is +/- 10 of the stimulus's location: (let ((x (add-linear-variance (x-of trigger-instance) 10)) (y (add-linear-variance (y-of trigger-instance) 10))) (cond ;; Check that the new location is within the known-world ;; boundaries. If so, create the new location instance: ((and (<= -50 x 50) (<= -50 y 50)) (make-instance 'location :time time :x x :y y :previous-location trigger-instance)) ;; Otherwise, tell the user that we've walked too far away: (t (format t "~2&Walked off the world: (~d, ~d).~%" x y)))))))) (define-ks random-walk-ks :trigger-events ((instance-created-event location)) :rating 100 :execution-function 'random-walk-ks-function) ;;; ==================================================================== ;;; Print-walk KS (defun print-walk-ks-function (ksa) ;;; Starting with the initial location instance, print the instance ;;; name and location of the walk (declare (ignore ksa)) (format t "~2&The random walk:~%") (let ((instance (find-instance-by-name 1 'location))) (while instance (format t "~s (~s ~s)~%" (instance-name-of instance) (x-of instance) (y-of instance)) (setf instance (next-location-of instance)))) ;; Tell the Agenda Shell to exit: ':stop) (define-ks print-walk-ks :trigger-events ((quiescence-event)) :rating 100 :execution-function 'print-walk-ks-function)
<install-dir>/initiate.lisp
gbbopen-modules
directory
Create a directory named gbbopen-modules
[~]$ mkdir gbbopen-modules [~]$This is a special directory that is read by used by GBBopen to find applications when GBBopen is started using
<install-dir>/initiate.lisp
Recall that you created a directory to hold the random-walk application in Step 1 of Working Within a File exercise. I used these shell commands to create my directories:
[~]$ mkdir tutorial [~]$ cd tutorial [~/tutorial]$ mkdir source [~/tutorial]$Then you created the
tutorial-example.lisp
source
subdirectory. We said that we would explain why we created the
source
directory in a later exercise. Well, later has arrived.
Each GBBopen application is packaged in a directory that contains:
modules.lisp
source
containing all the source files for the
module or application
commands.lisp
gbbopen-commands.lisp
source
directory and the
tutorial-example.lisp
modules.lisp
commands.lisp
Use your Common Lisp editor to create a new file named
modules.lisp
tutorial
directory (just as you
created the tutorial-example.lisp
source
subdirectory, but in the tutorial
directory that contains the source
subdirectory.
Type the following two forms into the new modules.lisp
(in-package :module-manager-user) (define-module :tutorial (:requires :agenda-shell-user) (:files "tutorial-example"))and then save the file.
Recall that the in-package
modules.lisp
:module-manager-user
The second form defines our application module, which we will name
:tutorial
. The :requires
subform specifies that the
:agenda-shell-user
:tutorial
module. The :files
subform
specified the files that comprise the module. In our case, there is one file:
tutorial-example.lisp
.lisp
file
extension, as the Module Manager will add the
appropriate source or compiled file extension for us.
gbbopen-modules
directory
The gbbopen-modules
gbbopen-modules
tutorial
directory there. However, it is generally more convenient to use a symbolic
link to point to the actual application directory. For example, an
application can be provided to a number of users by creating a symbolic link
to the application directory in each user's gbbopen-modules
Unless you are running Windows, add the random-walk application to your
gbbopen-applications
[~]$ cd ~/gbbopen-modules/ [~/gbbopen-modules]$ ln -s ~/tutorial . [~/gbbopen-modules]$
Instead of creating a symbolic link, GBBopen also supports a special “pseudo
symbolic-link” file that can be used with Windows. This is simply a text
file with the file extension .sym
that contains the target directory
path as the sole line in the file. For example, you could create the file
tutorial.sym
gbbopen-modules
C:\tutorial\as the sole line in the file.
:tutorial
module definition
Let's try out our module definition. Exit Common Lisp and start a fresh
Common Lisp session. If you have set up your environment according to the
Enhancing Your Development Environment exercise,
the following files should be loaded:
...
;; Loading <homedir>/shared-init.lisp
;; Loading <install-dir>/initiate.lisp
;; GBBopen is installed in <install-dir>
;; Your "home" directory is <homedir>
;; Loading <install-dir>/extended-repl.lisp
;; Loading <install-dir>/commands.lisp
;; Loading <install-dir>/gbbopen-modules-directory.lisp
;; No shared module command definitions were found in <install-dir>/gbbopen-modules/.
;; No personal module command definitions were found in <homedir>/gbbopen-modules/.
cl-user>
Note that some basic GBBopen initialization files have been loaded for us as
well as GBBopen's command definitions and any command definitions for
applications linked from our <homedir>/gbbopen-modules/
<install-dir>/initiate.lisp
.
Now, instead of loading the :agenda-shell-user
:module-manager-user
cl-user> :module-manager-user
;; Loading <install-dir>/startup.lisp
...
;; Loading <install-dir>/<platform-dir>/module-manager/module-manager-user.fasl
;; Loading <install-dir>/modules.lisp
;; No shared module definitions were found in <install-dir>/gbbopen-modules/.
;; Loading personal module definitions from <homedir>/gbbopen-modules/...
;; Loading <homedir>/tutorial/modules.lisp
module-manager-user>
Note that when the Module Manager was loaded as part of loading the
:module-manager-user
<homedir>/tutorial/modules.lisp
Now that we have defined our :tutorial
module, we can use the
:cm
, to compile (if
needed) and load it. Before doing so, however, let's explore what is happening
when :tutorial
is being compiled by instructing the Module Manager not
to create new compiled-file directories automatically (its default behavior).
Enter the following in the REPL:
module-manager-user> (setf *automatically-create-missing-directories* nil) nil module-manager-user>>
Now compile and load the :tutorial
module:
module-manager-user> :cm :tutorial
;; Loading <install-dir>/<platform-dir>/tools/preamble.fasl
...
;; Loading .../gbbopen/control-shells/agenda-shell-user.fasl
Error: Directory <homedir>/tutorial/<platform-dir>/
in module :tutorial does not exist.
Restart actions (select using :c n):
0: Create this directory.
1: Create this directory and any future missing directories.
module-manager-user>>
The :requires
in our :tutorial
module definition causes the
:agenda-shell-user
:tutorial
does not exist. Compiled files are put in a Common
Lisp and platform-specific subdirectory, <platform-dir>
tutorial
directory that mirrors the source
directory.
This organization makes it easy to use the application with a number of Common
Lisp implementations and on a file system shared with a number of different
hosts and operating systems.
By default, the Module Manager would have created the missing
:tutorial
module), but we
disabled automatic directory creation by setting
nil
.
We still could have avoided this continuable error by providing the
:create-dirs
:cm
command:
module-manager-user> :cm :tutorial :create-dirsto allow the Module Manager to create the
<platform-dir>
Restart actions (select using :c n): 0: Create this directory. 1: Create this directory and any future missing directories. module-manager-user>> :c 0 ;; Compiling file <homedir>/tutorial/source/tutorial-example.lisp ;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl module-manager-user>
At this point, we've compiled and loaded our :tutorial
application
module.
It is convenient to define a REPL command to compile and load you application (and any required GBBopen modules).
Use your Common Lisp editor to create a new file named
commands.lisp
tutorial
directory. Type the
following two forms into the new commands.lisp
(in-package :common-lisp-user) (define-repl-command :tutorial (&rest options) "Compile and load the Random-Walk Tutorial Application Module" (startup-module :tutorial options :gbbopen-user))and then save the file. A
commands.lisp
:common-lisp-user
The :tutorial
:tutorial
module will be compiled (if necessary) and
then loaded by the Module Manager when the
:
tutorial command is issued. The second, options
, argument
passes any options given with the command to a :gbbopen-user
:gbbopen-user
:tutorial
module is loaded.
:tutorial
command
Let's try our command definition. Exit Common Lisp and start a fresh
Common Lisp session. If you have set up your environment according to the
Enhancing Your Development Environment exercise,
the following files should be loaded:
...
;; Loading <homedir>/shared-init.lisp
;; Loading <install-dir>/initiate.lisp
;; GBBopen is installed in <install-dir>
;; Your "home" directory is <homedir>
;; Loading <install-dir>/extended-repl.lisp
;; Loading <install-dir>/commands.lisp
;; Loading <install-dir>/gbbopen-modules-directory.lisp
;; No shared module command definitions were found in <install-dir>/gbbopen-modules/.
;; Loading personal module command definitions from <homedir>/gbbopen-modules/...
;; Loading <homedir>/gbbopen-modules/tutorial/commands.lisp
cl-user>
Note that the commands.lisp
tutorial
directory has been loaded by
<install-dir>/initiate.lisp
Now, we can compile and load the :tutorial
module by simply issuing the
:tutorial
REPL command:
cl-user> :tutorial
;; Loading <install-dir>/startup.lisp
...
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
gbbopen-user>
With the command definition in place, we are able to compile and load our
random-walk application by issuing a single command, :tutorial
.
Note a potential continuable error due to a missing
<platform-dir>
:create-dirs
:tutorial
command:
gbbopen-user> :tutorial :create-dirsto allow the Module Manager to create the
<platform-dir>
<platform-dir>
:create-dirs
If you prefer, you can specify that the Module Manager not create missing
<platform-dir>
:create-dirs
:common-lisp-user
t
by
default. If you would like to turn-off automatic directory creation by the
Module Manager, add the following form to your shared-init.lisp
(defparameter *automatically-create-missing-directories* nil)
I prefer to have the Module Manager generate the continuable error if it needs
to create a <platform-dir>
:create-dirs
t
in my
shared-init.lisp
We have been developing our random-walk application in GBBopen's
:gbbopen-user
:gbbopen-user
:gbbopen-user
To eliminate this possibility, we can create our own package for the
random-walk application. First, let's determine what packages are being used
by GBBopen's :gbbopen-user
gbbopen-user> (package-use-list :gbbopen-user) (#<package PORTABLE-THREADS> #<package AGENDA-SHELL> #<package MODULE-MANAGER> #<package COMMON-LISP> #<package GBBOPEN-TOOLS> #<package GBBOPEN>) gbbopen-user>
tutorial-example.lisp
We want our new :tutorial
package to use the same packages that the
:agenda-shell-user
tutorial-example.lisp
:gbbopen-user
(in-package :gbbopen-user)with the following:
(eval-when (:compile-toplevel :load-toplevel :execute) (unless (find-package :tutorial) (defpackage :tutorial (:use :common-lisp :module-manager :gbbopen-tools :gbbopen :portable-threads :agenda-shell)))) (in-package :tutorial)and save the file.
Note the use of eval-when
:tutorial
package when needed, whether
the file is being compiled or loaded. The eval-when
:compile-toplevel
:load-toplevel
:execute
) provides this behavior to
the forms that it contains. Such eval-when
In my applications, I also add a feature to Common Lisp's *features*
list to indicate that the application has been fully loaded. To do this, add
the following at the end of your tutorial-example.lisp
(pushnew :tutorial *features*)and save the file.
commands.lisp
Next, edit your commands.lisp
:gbbopen-user
(define-repl-command :tutorial (&rest options) "Compile and load the Random-Walk Tutorial Application Module" (startup-module :tutorial options :gbbopen-user))and add the package-name
:tutorial
in its place:
(define-repl-command :tutorial (&rest options) "Compile and load the Random-Walk Tutorial Application Module" (startup-module :tutorial options :tutorial))Save the file.
modules.lisp
Finally, we no longer need the :gbbopen-user
:agenda-shell-user
:tutorial
module that we defined:
gbbopen-user> (describe-module :tutorial) Module :tutorial (loaded) Requires: (:agenda-shell-user) Fully expanded requires: (:module-manager :module-manager-user :portable-threads :gbbopen-tools :gbbopen-core :polling-functions :queue :agenda-shell :os-interface :gbbopen-user :agenda-shell-user) Source directory: <homedir>/tutorial/source/ Compiled directory: <homedir>/<platform-dir>/ Forces recompile date: None Files: Mar 24 06:02 tutorial-example gbbopen-user>Although we only specified that the
:agenda-shell-user
:tutorial
module implicitly requires a number of
packages that are required by the :agenda-shell-user
If we look at the details of the :agenda-shell-user
gbbopen-user> (describe-module :agenda-shell-user) Module :agenda-shell-user (loaded) Requires: (:agenda-shell :gbbopen-user) Fully expanded requires: (:module-manager :module-manager-user :portable-threads :gbbopen-tools :gbbopen-core :polling-functions :queue :agenda-shell :os-interface :gbbopen-user) Source directory: <install-dir>/source/gbbopen/control-shells/ Compiled directory: <install-dir>/<platform-dir>/gbbopen/control-shells/ Forces recompile date: None Files: Mar 23 12:27 agenda-shell-user gbbopen-user>Note that the
:agenda-shell-user
:agenda-shell
:gbbopen-user
:gbbopen-user
modules.lisp
:agenda-shell-user
:requires
option in our :tutorial
module definition:
(in-package :module-manager-user) (define-module :tutorial (:requires :agenda-shell-user) (:files "tutorial-example"))and replace it with
:agenda-shell
(in-package :module-manager-user) (define-module :tutorial (:requires :agenda-shell) (:files "tutorial-example"))Save the file.
Let's make sure that everything is still working. Exit Common Lisp and start
a fresh Common Lisp session. Next enter the
:tutorial
REPL command:
cl-user> :tutorial
;; Loading <install-dir>/startup.lisp
...
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
tutorial>
Note that we are now in our newly defined :tutorial
package. We should
still be able to run the random-walk application:
tutorial> (start-control-shell)
;; Control shell 1 started
Walked off the world: (56, 38).
The random walk:
1 (0 0)
2 (-1 -1)
3 (-8 -10)
4 (0 -2)
5 (-5 2)
6 (3 11)
7 (8 5)
8 (12 2)
9 (3 12)
10 (10 4)
...
55 (50 40)
56 (42 47)
57 (47 41)
;; Explicit :stop issued by KS print-walk-ks
;; Control shell 1 exited: 60 cycles completed
;; Run time: 0.01 seconds
;; Elapsed time: 0 seconds
:stop
gbbopen-user>
GBBopen also has an
<install-directory>/shared-gbbopen-modules
gbbopen-modules
shared-gbbopen-modules
This is the recommended mechanism for installation-wide managing and sharing
of modules and applications, and if we wanted to share our random-walk
application to everyone using our GBBopen installation, we could create our
symbolic link (or “pseudo-symbolic-link” file) in the
shared-gbbopen-modules
Suppose we want the random-walk application to run automatically when it is loaded. You could simply add:
(start-control-shell)as a top-level form at the end of your
tutorial-example.lisp
GBBopen's Module Manager Facility supports a convention that makes it easy to
conditionalize load-time action execution via the value of
nil
when a module is loaded with the :noautorun
option.
Add the following at the end of your tutorial-example.lisp
(when *autorun-modules* (format t "~{~&~s~%~}" (multiple-value-list (start-control-shell))))and save the file. We could have simply called
*autorun-modules*
format
form above prints each returned value on a
separate output line.
By convention, the “autorun” form is placed at the very end of the file,
immediately after the form to add :tutorial
to Common Lisp's
*features*
. This is so that the :tutorial
feature will be
present during the “autorun” execution and thereafter—even if an error
occurs when executing the “autorun” form.
Enter the :tutorial
REPL command. The modified
tutorial-example.lisp
tutorial> :tutorial
;; Compiling <homedir>/tutorial/source/tutorial-example.lisp
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
;; Control shell 1 started
Walked off the world: (11, -51).
The random walk:
1 (0 0)
2 (6 7)
3 (6 3)
4 (3 -4)
5 (10 -13)
...
21 (-5 -46)
22 (-9 -39)
23 (1 -33)
24 (8 -41)
;; Explicit :stop issued by KS print-walk-ks
;; Control shell 1 exited: 27 cycles completed
;; Run time: 0.01 seconds
;; Elapsed time: 0 seconds
:stop
tutorial>
Let's try it again:
tutorial> :tutorial
tutorial>
This time, nothing happened. Why?
Since no source files were modified, the Module Manager knows that the latest
compiled files for the :tutorial
module and its required modules have
all been loaded. So, because the tutorial-example
We can tell the Module Manager to always reload the
tutorial-example
modules.lisp
:reload
file option to the :tutorial
module
definition:
(define-module :tutorial (:requires :agenda-shell) (:files ("tutorial-example" :reload)))Note that once a file has one or more options, the file name and its options are enclosed in parentheses.
Save the modified modules.lisp
:tutorial
command, the tutorial-example
tutorial> :tutorial
;; Loading <homedir>/tutorial/modules.lisp
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
;; Control shell 1 started
...
tutorial>
The Module Manager noticed our updated modules.lisp
:tutorial
module definition, and then
followed our :reload
specification.
Let's try the :tutorial
command one more time, just to be certain that
:reload
is happening when no files have been updated:
tutorial> :tutorial
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
;; Control shell 1 started
...
tutorial>
The Module Manager's :reload
option, so we might be tempted to simply add that option when
we specify our :tutorial
command:
tutorial> :tutorial :reload ;; Loading <install-dir>/<platform-dir>/module-manager/module-manager.fasl ;; Loading <install-dir>/<platform-dir>/module-manager/module-manager-user.fasl ... ;; Control shell 1 started ... tutorial>however, this would also reload all the files of very module required by the
:tutorial
module, as the :tutorial
:propagate
:nopropagate
:tutorial
tutorial> :tutorial :reload :nopropagate
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
;; Control shell 1 started
...
tutorial>
which would eliminate reloading of all but the files in the :tutorial
module. Since we only have one file (at the moment) specified in our
:tutorial
module definition, this behavior is equivalent, but less
convenient, than specifying the :reload
file option to our
tutorial-example
The Module Manager also remembers the last module (and any provided options,
such as :create-dirs
:cm
(textbfcompile-module) or :lm
(:tutorial
REPL command performs an implicit :cm
command for us,
so we could have alternatively typed the :cm
REPL command rather than
:tutorial
once we have issued the first :tutorial
REPL command:
tutorial> :cm
;; :cm :tutorial :propagate
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
;; Control shell 1 started
...
tutorial>
Just the thing for lazy typists like me! Note that the :cm
command
echos the full (implicitly completed) command with the remembered module name
and any remembered options.
Reloading the entire tutorial-application
autorun.lisp
source
subdirectory of the
tutorial
directory. Type (or copy) the following two forms into the new
autorun.lisp
(in-package :tutorial) (when *autorun-modules* (format t "~{~&~s~%~}" (multiple-value-list (start-control-shell))))and save the file.
Next, edit the tutorial-example.lisp
(when *autorun-modules* (format t "~{~&~s~%~}" (multiple-value-list (start-control-shell))))Save the file.
We could define a second random-walk application module, say
:run-tutorial
modules.lisp
autorun.lisp
:tutorial
module. This definition would look like:
(define-module :run-tutorial (:requires :tutorial (:files ("autorun" :reload))))However, we can avoid creating a new module by simply adding the
autorun
file to our current :tutorial
module definition.
Edit the modules.lisp
tutorial
directory and
remove the :reload
file option from the :tutorial-example
(define-module :tutorial (:requires :agenda-shell) (:files ("tutorial-example" :reload)))and then add a new line for the
autorun
file with the :reload
file option:
(define-module :tutorial (:requires :agenda-shell) (:files "tutorial-example" ("autorun" :reload)))and save the file.
Let's double-check that everything is working:
tutorial> :cm
;; :cm :tutorial :propagate
;; Loading <homedir>/tutorial/modules.lisp
;; Compiling <homedir>/tutorial/source/tutorial-example.lisp
;; Loading <homedir>/tutorial/<platform-dir>/tutorial-example.fasl
;; Compiling <homedir>/tutorial/source/autorun.lisp
;; Loading <homedir>/tutorial/<platform-dir>/autorun.fasl
;; Control shell 1 started
...
tutorial>
The modified files are compiled and loaded and the Agenda Shell is invoked.
Let's try it one last time, just to be sure that the application runs when no
files have been modified:
tutorial> :cm
;; :cm :tutorial :propagate
;; Loading <homedir>/tutorial/<platform-dir>/autorun.fasl
;; Control shell 1 started
...
tutorial>
Congratulations! It's time to move to the next exercise.
The GBBopen Project
![]() | ![]() | ![]() | Creating a GBBopen Application | ![]() |