![]() | ![]() | ![]() | 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.lispgbbopen-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.lispRecall 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.lispsource 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.lispsource containing all the source files for the
module or application
commands.lispgbbopen-commands.lispsource directory and the
tutorial-example.lispmodules.lispcommands.lispUse your Common Lisp editor to create a new file named
modules.lisptutorial directory (just as you
created the tutorial-example.lispsource 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-packagemodules.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-modulesgbbopen-modulestutorial
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.symgbbopen-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.lisptutorial 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-userThe :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.lisptutorial
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-dirsIf you prefer, you can specify that the Module Manager not create missing
<platform-dir>:create-dirs:common-lisp-usert 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-dirst 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.lispWe want our new :tutorial package to use the same packages that the
:agenda-shell-usertutorial-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.lispNext, 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-userIf 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-usermodules.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-modulesgbbopen-modulesshared-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.lispGBBopen'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-examplemodules.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-applicationautorun.lispsource 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-tutorialmodules.lispautorun.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.lisptutorial 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 | ![]() |