Next: Introduction [Contents][Index]
This manual describes gzochid, the gzochi server.
Copyright © 28 December 2020 Julian Graham.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled “GNU Free Documentation License”.
This reference manual documents gzochid, the server component of the gzochi massively multiplayer online game development framework.
Getting started | ||
---|---|---|
• Introduction | ||
• Conceptual overview | ||
• Installation | ||
• Running gzochid | ||
The gzochid container | ||
• Application deployment | ||
• Application services | ||
• Communication protocol | ||
• User authentication | ||
• Monitoring | ||
• Remote debugging | ||
Writing applications for gzochid | ||
• Application design with gzochid | ||
• Scheme API reference | ||
• An example application | ||
Administration | ||
• Database tools | ||
Appendices | ||
• GNU Free Documentation License | ||
Indices | ||
• Concept index | ||
• Procedure index | ||
• Type index |
Next: Conceptual overview, Previous: Top, Up: Top [Contents][Index]
gzochid is the server component for the gzochi massively multiplayer online game development framework. It is responsible for hosting the server-side portions of game applications developed against the framework, for managing the data persisted by these applications and provisioning computing resources for their needs at run-time, and for exposing a set of container services that are especially useful for online game development.
gzochid is a container for game server applications the same way that a web container is a container for web applications: It manages the lifecycles of the applications it hosts, routes client requests to application endpoints, and provides services to ease the interaction of the applications with external resources.
This manual expects a familiarity with the Scheme programming language, which is the language used to write applications that can be hosted by gzochid. gzochid uses the GNU GUile extension language platform to provide its Scheme run-time environment; the Guile manual is an excellent resource for users who are new to Scheme or to functional programming in general.
New users of gzochi may wish to pay special attention to the following section of this manual, “Conceptual overview,” which explores some of the rationale for the design of the gzochi framework and the services it provides.
Next: Installation, Previous: Introduction, Up: Top [Contents][Index]
Developing games that are played over a computer network poses challenges distinct from those that arise from games whose extent is limited to a single process or a single machine. The characteristics of the network link that connects the machines involved the game give rise, by necessity, to some primary constraints on the design of the game: To create a dynamic model of the game state that is shared between the client and the server, those systems must exchange messages, and this process is naturally limited by the rate of message delivery. Will messages be delivered reliably? Is message order guaranteed to be preserved? Answers to these questions will also have an impact on game server architecture.
When multiple players are allowed to interact with a networked game system simultaneously, an additional set of difficulties emerges. How can their interactions with components of the game system be properly synchronized such that each player receives a fair and consistent view of the world? How can this synchronization be scaled to maintain a responsive user interface for up to thousands of simultaneously connected clients?
Some additional background is presented in the following sections. To address these issues, gzochid provides a unified set of software tools, which are described in a subsequent chapter (see Application services).
• Data persistence | ||
• Transactions and atomicity | ||
• Network communication | ||
• Concurrent execution |
Next: Transactions and atomicity, Up: Conceptual overview [Contents][Index]
If the state of a game is to “outlive” the server process that manages it, the data that makes up the state must be written to non-volatile storage, such as a hard disk. There are many reasons for a server process to terminate. Some shutdowns, such as those performed for system maintenance or upgrades; others are spontaneous—software bugs are inevitable, and whether they cause the server process to terminate outright or leave the game system in an unplayable state, the net effect is the same. When a player’s commitment to an online game can span months or even years, losing their play history can be quite disheartening.
Several questions must be answered in order for persistence to be implemented: How often must data be persisted? If players are notified of changes to game state before these changes are made persistent, then the potential exists for a recovered game state to “erase” progress made by players between the last persistence event and the shutdown of the game. On the other hand, non-volatile storage media are often quite a bit slower than random-access memory, and thus forcing persistence too often can create a bottleneck on game performance. How much of the game state must be persisted at each persistence point to ensure that the entire state can be reconstituted at some later date? For large, complex games, the game state may be so large that persisting the entire state takes so long that even infrequent persistence events must cause the game to “pause.” If only portions of the game state are persisted, then care must be taken to ensure that enough state is persisted to ensure logical consistency between entities within the game.
gzochid addresses these issues with an automatic persistence mechanism that treats game state as an object graph in which updates are tracked and persisted transparently, with full transactional semantics. See Managed records, for more information.
Next: Network communication, Previous: Data persistence, Up: Conceptual overview [Contents][Index]
It is often important that certain sequences of events occur as a unit, such that either every event in the sequence takes place (and players receive notification) or, in the event of an error, none of the events take place—no matter where in the sequence the error arises. One example of this is transferring an object from one the inventory of one player to the inventory of another. Depending on how the transfer is implemented, there may be an interval between the object being taken from the first player but before it is given to the second player. If the execution of game code is interrupted at this point, the object may be destroyed. A similar problem arises if a reference to the object is given to the second player before being removed from the first.
Even when errors are detected, recovery can difficult: It may not be possible to determine what the state of individual connected clients is once they have lost consistency with the server’s model of the game world, and enabling clients to resolve conflicting updates to game state presents significant architectural challenges. In general, once the side effects—e.g, client notifications or persistent changes to data—have been committed, they cannot easily be undone.
gzochid executes all game application code in a transactional context, meaning that the side effects produced by a portion of code are delayed until its successful completion, at which point all of them guaranteed to take effect together; or the side effects are “rolled back” and the block of code is either retried, with no indication to the client that an error occurred, or abandoned. And other portions of code that are concurrently accessing the same bits of data are guaranteed a consistent view of that data for the duration of their execution.
Next: Concurrent execution, Previous: Transactions and atomicity, Up: Conceptual overview [Contents][Index]
The reliable delivery of messages between clients and the server is fundamental to the operation of an online game, but the network communication services provided by most programming languages and operating systems expose game applications to nuanced, unpredictable behaviors. Some communication channels do not guarantee packet delivery; nor is in-order delivery alway ensured. Even if a system promises ordered and reliable ordered delivery of packets, it is difficult to predict their time of delivery.
When a packet of data arrives on the client or server, the bytes it contains must be interpreted, a process that is governed by a shared protocol. The set of possible communications between hosts in an online game is naturally dependent upon the specific mechanics of that game, but some types of messages are likely common to all games: Clients need to establish their identities with the server; the server needs to respond to authentication attempts. The client and server may need messages representing attempts to disconnect or log out of the game gracefully. Some types of messages, which trigger transitions in the state of a client’s connection to the server, are only valid when the connection is in a particular state. What should be the response from the server if it receives a login request from an already-authenticated client in the course of regular gameplay? If there are multiple points in the flow of game execution at which messages can be received, does semantic validation of message content have to be applied at all of them?
Because the state of a network connection is dependent on the state of multiple independent hosts and their operators, it is inherently asynchronous with game state. In the likely case that a protocol message is larger than a single packet, an entire message may not become available all at once, and thus participants in the communication will need to keep a buffer of partial messages while they wait for the remaining bytes. A connection may be interrupted before all of the message’s consituent packets have been delivered, and if the processing of network data is mixed in with the processing of game application logic, recovery from a network failure may be complicated.
There are aspects of multiplayer games for which it is not very important that every player have a view of the world that is consistent with that of every other player. For example, depending on the context, it may not be critical that every player have the same information about the scenery in a particular region—this is information that is related to the game world, but has no bearing on the outcome of the game. There are other elements of gameplay for which it is necessary that all involved players are kept in a consistent state. If some but not all players receive notification of a change that does affect the outcome of the game, not only are they at a strategic disadvantage, but losing synchronization with the server’s model of the world may make it difficult for them to interpet subsequent notifications.
gzochid provides a network management layer and a low-level client-server protocol that work in concert to hide the tricky details of message deliery. Reasonably large messages may be transactionally queued for delivery to the server or its clients, and messages can be addressed to individual clients or to arbitrarily large groups of clients with a single procedure invocation. See Client session management, for information on gzochid’s representation of client connections; See Channel management, for a description of efficient message broadcast services.
Previous: Network communication, Up: Conceptual overview [Contents][Index]
While some portions of a game can be driven entirely by client-generated events, there are often flows of execution that are best managed as “background processes” that proceed independent of any action by a client. Some examples of this type of processing include: Time and weather systems, in which regular changes to the game world take place at scheduled intervals; or the actions of non-player characters that act as autonomous agents within the game world, with a flow of logic that determines their behavior based on various dynamic game states.
As you can infer from these examples, different modes of scheduling and execution are possible for tasks within the same application. Some tasks need to begin executing at predetermined points in the future. Others need to run as soon as CPU time can be allocated to them. Certain tasks, such as those implementing an in-game timer tick, for example, may need to execute on a repeating basis.
This type of asynchronous parallelism could be implemented using a an explicitly threaded execution model, but this approach has some significant limitations. For one, allocating a new thread for every asynchronous bit of processing to be done consumes valuable process memory and CPU cycles, and effectively constrains the amount of work that can be . A thread pool can some of these issues, but few low-level thread libraries include time-based scheduling as part of their thread management APIs—you can specify that some bit of code should run in a separate thread but, but you usually can’t control when it runs or how often it repeats without building some abstractions around the system’s thread primitives.
gzochid provides scheduling and execution services that allow tasks to be queued for immediate or deferred processing, either one time only or repeating on a configurable interval. Furthermore, gzochid provides durability guarantees about scheduled tasks to the effect that tasks that fail with a recoverable error can be retried, and that the universe of scheduled tasks can survive a failure and restart of the application server. See Task scheduling, for more information.
Next: Running gzochid, Previous: Conceptual overview, Up: Top [Contents][Index]
See the INSTALL file included in the gzochid distribution for detailed instructions on how to build gzochid. In most cases, if you have the requisite toolchain and dependences in place, you should simply be able to run
./configure make make install
This will install the gzochid executable gzochid (as well as the database tools gzochi-dump, gzochi-load, and gzochi-migrate) to a standard location, depending on the installation prefix—on most Unix-like systems, this will be /usr/local, with executable files being copied to /usr/local/bin. A server configuration file with the default settings will be installed to the /etc directory under the installation prefix—by default, /usr/local/etc.
This configuration file will be processed prior to installation to set references to the gzochid deployment and data directories—where the server looks for deployed games and where it stores game state data, respectively—to locations relative to the installation prefix. See The server configuration file, for more information.
Next: Application deployment, Previous: Installation, Up: Top [Contents][Index]
The format for running the gzochid
program is:
gzochid option …
With no options, gzochid
scans its deployment directory
looking for game applications, each of which is configured and
initialized or restarted depending on its state, and then begins
listening for client connections. By default, the monitoring web
application is also started.
In the absence of command line arguments, the port numbers on which these servers listen for connections, as well as other aspects of their behavior, are modifiable via a configuration file (see below).
gzochid
supports the following options:
Specify an alternate location for the server configuration file.
Print an informative help message on standard output and exit successfully.
Print the version number and licensing information of gzochid on standard output and then exit successfully.
• The server configuration file |
Up: Running gzochid [Contents][Index]
The gzochid server configuration file is usually named gzochid.conf and is installed (and searched for) by default in the /etc/ directory of the installation prefix. It is an .ini-style configuration file, meaning it consists of several named sections of key-value pairs, like so:
[section] key1 = value1 key2 = value2
The configuration options currently understood by gzochid are as follows, organized by section.
admin
These settings control various aspects of gzochid’s administrative and monitoring functionality.
Set to true
to enable the debugging server.
The local port on which the debugging server should listen for incoming telnet connections.
Set to true
to enable the monitoring web server.
The local port on which the monitoring web server should listen for incoming HTTP connections.
game
These settings control the primary game server module of gzochid.
The local port on which to listen for incoming TCP connections from gzochi clients.
The filesystem directory in which to store game state data for hosted game applications. The user associated with the gzochid process must have read and write access to this directory, as the server will attempt to create sub-directories rooted at this location for each game, and will read and write data files in those sub-directories.
The filesystem directory to search for deployed game applications. The user associated with the gzochid process must have read and execute access to this directory—but it does not need to be able to write files here. This may be an absolute or relative path; if relative, it is resolved relative to the location of the gzochid.conf file.
The filesystem directory to probe for application authentication plugin modules. Every regular file in this directory will be examined, and an attempt will be made to load each as a dynamic library and initialize the authentication plugin interface. The user associated with the gzochid process must have read access to this directory.
The parent directory for storage engine libraries. This path is used to resolve the absolute location of the storage engine module specified by “storage.engine” (see below). The user associated with the gzochid process must have read access to this directory.
The name of the storage engine to use to store game application data. If the value of this setting is given as “mem” then gzochid’s built-in B*tree-based storage engine will be used (note that this is a transient data store, not suitable for production deployments); otherwise, this name is used in conjunction with the value of “storage.engine.dir” to resolve the absolute location of a dynamic library file that exports the gzochid storage engine interface.
In contrast with the authentication plugin load process, which examines all files in the plugin directory; only the library file that corresponds to the named storage engine will be loaded and inspected and boot time. All applications running within the container share the same storage engine, although each maintains its own databases.
The number of threads in the task execution thread pool used by the container to execute callbacks for application lifecycle events (initialized, ready) client events (logins, received messages, disconnects) and tasks scheduled by the callbacks for these events (and by other tasks).
Given that the bodies of these work items should be largely compute-bound, it is generally a good idea to pin the size of this thread pool to the number of logical CPU cores on the machine running the container.
The maximum duration, in milliseconds, for time-limited transactions executed on behalf of a game application. Any task or callback whose execution time exceeds this value will fail and be retried (if it has not used up its maximum retries). This setting also bounds the amount of time the container’s data services will wait to obtain a lock for exclusive access to any single datum such as a managed reference; if the wait time expires, the transaction attempting to access the data will be marked for rollback.
By design, this setting has an impact on the task execution throughput of game applications. The longer a task takes to execute, and the more data it accesses as part of its execution, the more constraints it places on the transactional work that can be done concurrent with its execution. As a corollary, the shorter a task’s duration and the less data it accesses, the less risk there is that it will conflict with other tasks. Before increasing this value beyond its default, consider refactoring a failing task to perform fewer operations and access less data.
Certain lifecycle events, such as application initialization, are executed in transactions without time limits; this setting has no effect on the processing of those events.
log
These settings control the system-wide logging behavior of gzochid.
The lowest severity of log message that will be recorded in the logs
(both to console and to the server’s log file). Valid values of this
settings are, in order of decreasing severity: ERR
,
WARNING
, NOTICE
, INFO
, and DEBUG
.
Next: Application services, Previous: Running gzochid, Up: Top [Contents][Index]
Game applications hosted by gzochid are discovered by the server when it scans its deployment directory for sub-directories containing game application descriptor files, which are discussed in the following section. If gzochid’s game deployment directory is “/usr/local/share/gzochid/deploy” (the default if installing from the source distribution), then the server will scan and discover the game descriptor file “/usr/local/share/gzochid/deploy/my-game/game.xml” on startup.
The other required components of a game application are the Guile Scheme modules that contain the game logic and callbacks. These are typically included in sub-directory trees rooted in the game application directory, but alternate locations to search for modules can be specified in the game application descriptor file.
• The game application descriptor |
Up: Application deployment [Contents][Index]
“game.xml,” the game application descriptor, is an XML document that is deployed as part of a game application and provides game configuration information and metadata to gzochid. It is a rough analog to the “web.xml” descriptor file that accompanies Java web applications. Like “web.xml,” “game.xml” is used by the server to find the locations of the application’s code libraries and the entry points to the application. The game descriptor file must appear in the deployed application’s root directory.
A DTD that can be used to validate the structure of your application descriptors is included in the gzochid source distribution. Some explanation of the semantics of the descriptor file follows.
The application descriptor’s document element is game
, and it
must include a name
attribute that specifies a unique
“short name” for the application; among other things, this name
will be used to identifier your application in log messages.
The description
element allows you to specify as its content
some longer, more descriptive text to identify your game. (This text
will be displayed in the gzochid monitoring console.)
The load-paths
element is a wrapper element for zero or more
load-path
element, each of which specifies as its content an
absolute or relative path to be (temporarily) added to the default
Guile %load-path
variable, which contains the locations that
are searched during module resolution. Note that the game
application root directory is always added to the load path, so you
can leave the load-paths
element empty if your application
library tree is rooted at the application root.
Next there are two “lifecycle callbacks” that must be specified,
procedures for handling the “initialization” and “logged in”
events. Callbacks are specified via a callback
element, which
has no content but requires the procedure
and module
arguments which specify a publicly-visible Scheme procedure to be
used to handle an event. For example, the following XML snippet:
<callback procedure="handler" module="my-code app handlers" />
...identifies the handler
procedure exported from the Guile
module (my-code app handlers)
. Modules are resolved via the
configured load paths.
Initialization occurs once per application—even across server restarts—and the initialization callback is passed an R6RS hashtable containing application properties. (In the current release, this table will always be empty.) The logged in callback will be called when a client connects and successfully authenticates, and it will be passed a client session record that can be used to communicate with the connected client.
Next: Communication protocol, Previous: Application deployment, Up: Top [Contents][Index]
The follow sections describe the services that gzochid exposes to the applications it hosts. As an application author, you are free to employ or ignore them in whatever proportion you choose, but the expectation is that most non-trivial games will depend heavily on all of the services the container provides.
• Transaction management | ||
• Managed records | ||
• Data binding and storage | ||
• Client session management | ||
• Channel management | ||
• Task scheduling | ||
• Transactional logging |
Next: Managed records, Up: Application services [Contents][Index]
All game application code that executes within the gzochid container executes in the context of a transaction, meaning that all of its side effects, such as sending messages or modifying data, have the four ACID properties: Atomicity, consistency, isolation, and durability. The services described in the following sections are the primary means by which side effects are achieved in a gzochi application, and all of them participate in the container’s two-phase commit protocol, which verifies that each participant is prepared to commit before issuing the request to do so.
When a transaction commits, the side effects requested from each service by application code are made permanent: Messages are sent to clients, changes to data are saved to the data store. When a transaction fails to commit, each service participating in the commit rolls back any transaction-local state that was created during the lifetime of the transaction.
A transaction may fail to commit for any of several reasons: It may
have exceeded the maximum running specified by the tx.timeout
setting; it may have attempted to access data in a way that brought
it into conflict with another currently-executing transaction; an
external resource (such as a network connection) required by the
transaction may be unavailable; code executing in a transactional
context may exit non-locally. If a transactional task or callback
fails to commit but the container determines that it is safe and
desirable to re-attempt it, it will automatically be returned to the
queue of items eligible for execution. A transactional unit of work
that fails in a “retryable” way will be retried up to a configured
maximum number of times (3, by default) before being abandoned.
• External transactions |
Up: Transaction management [Contents][Index]
In some cases, an application running inside the gzochid container may be merely one part of a larger application deployment that includes other services that need access to events that originate within code running within the container. One example of this is a web “leaderboard” that reports the points scored by players in a gzochi game application. The web server serving this leaderboard needs access to data (the “points”) that results from transactions within the game application, but it is neither practical nor efficient to provide this information directly from the game data store. It would be convenient to allow this data to be written to a more appropriate store (such as a flat file or relational database) but none of the transactional game APIs—task scheduling, channel or session management—offer a promising mechanism for “leaking” transactional events from the container.
To serve use cases like the one described above, the the gzochid container provides an SPI for integrating external systems with the lifecycle of a transaction within the container. Game application code may use this SPI to register a proxy (or “participant”) with a gzochid transaction such that procedures attached to the proxy are invoked when during the ‘prepare’ and ‘commit’ or ‘rollback’ phases. Game application code may also signal through the participant that the external transaction must be rolled back, in turn triggering a rollback of the container’s transaction.
Note that the implementation of the participant’s transaction
lifecycle handlers must observe the rules of the transaction: If the
‘prepare’ handler is called and returns #t
, the ‘commit’
handler must commit the work of the transaction successfully; the
‘rollback’ handler may be called whether or not the ‘prepare’
handler is called first, and must completely roll back the work of
the transaction. See gzochi tx, for more information on the
external transaction SPI.
Next: Data binding and storage, Previous: Transaction management, Up: Application services [Contents][Index]
gzochid exposes its transactional persistence services via
flexible, user-defined data structures called “managed records.”
A managed record is just like an R6RS record—in fact, a managed
record is an R6RS record that has the record-type
gzochi:managed-record
as its parent—with a few additional
rules.
First of all, the fields of a managed record must be annotated to assist the container in persisting them to the data store. This is done via a serialization clause added to every field sub-clause in the managed record definition. The serialization clause gives a reference to the serialization to use to convert the field value to and from a stream of bytes as it is written to and read from the data store. A serialization is a record that provides serializer and deserializer procedures that are called by the container during the persistence phrase for managed records that have been modified in a transaction. The serialization clause may be omitted for fields whose value will always be a managed record. In this case, a built-in managed record serialization will be used. See gzochi io, for more information on serialization.
Second, managed record-type definitions must be nongenerative
(unique) with respect to their serialized type. Every managed record
type is associated with a symbol (called a “serial UID”) that
identifies it to the serialization system, and no two managed record
types may share the same serial UID. A record type’s serial UID may
be given explicitly, via the serial-uid keyword argument or
clause, or one will be selected automatically: By taking the record
type’s R6RS uid
(if the type is nongenerative) or by taking
the record type name.
All managed record types definitions are added to a type registry,
from which they are looked up by their serial UIDs during
serialization and deserialization. Record types that do not specify
a target type registry via the type-registry
keyword argument
or clause will be added to a default type registry. The
(gzochi data)
data module exports procedures that allow
developers to create new type registries, but this is not typically
useful outside the scope of a database migration, since during
normal operation of the gzochid container, only the default type
registry is used. See Migrating data, for more information on
data migration.
For parity with the R6RS record libraries, the gzochid Scheme API provides both syntactic and procedural facilities for working with managed records. See gzochi data, for more information on creating managed records.
Next: Client session management, Previous: Managed records, Up: Application services [Contents][Index]
gzochid provides a few different ways of accessing data persisted as managed records. You can access persistent data implicitly, in the form of a managed record used as a callback for a lifecycle event (e.g., client messages); or you can bind managed objects to names for explicitly retrieval. In either case, the container will also manage the persistence of managed records reachable from the root record—that is to say, gzochid knows when a field in one managed record contains a reference to another managed record and only persists the portions of the object graph that change during the execution of application code.
Like the rest of the operations provided by the container, access to data follows transactional semantics. Any single flow of execution within an application is guaranteed a consistent view of data over the lifetime of its execution, and the set of changes it makes to data during this lifetime will be persisted together or not at all. These operations are also subject to transactional constraints, such as the requirement that they complete within a configured timeout and that they not conflict with operations accessing the same data in other transactions. If these constraints are violated, the operation will fail and the task will be retried or abandoned.
Bindings can be manipulated using the gzochid:set-binding!
,
gzochid:get-binding
and gzochid:remove-binding!
procedures. For example, the following code snippet introduces a new
binding that refers to a field of the managed record obj:
(define obj (gzochi:get-binding "my-object")) (gzochi:set-binding! "f" (my-object-field obj))
Note that the container tracks references to managed records such
that at any subsequent point (provided the bindings above are not
mutated), the following expression will evaluate to #t
:
(eq? (my-object-field (gzochi:get-binding "my-object")) (gzochi:get-binding "f"))
See gzochi data, for more information.
Next: Channel management, Previous: Data binding and storage, Up: Application services [Contents][Index]
A connected and authenticated user is exposed to game code in the form of a client session, a managed record that can be used to manipulate the state of that user. Messages may be queued transactionally for delivery to individual client sessions or to groups of sessions (see below); sessions can be explicitly disconnected; and handlers may be registered for session-related events, such as incoming messages and client-side disconnections.
Because client sessions are managed records, they can be used any place in game application code that managed records may be used, such as automatically serialized fields of other managed records or as the data for an event handler. See gzochi client, for more information.
Next: Task scheduling, Previous: Client session management, Up: Application services [Contents][Index]
Message broadcasting is a pattern that appears frequently in massively multiplayer game development. There may be a logical set of players that should be managed and addressed by the server as a group for the purposes of messaging. This group may include every player in the game, in the case of system notifications that need to be sent globally; or it may correspond to a logical grouping arising from game logic, such as broadcasting messages only to players gathered in a particular room or region.
gzochid addresses this case by providing an abstraction for managing communication with arbitrarily large numbers of grouped client sessions. gzochid “channels” are managed records, and, like client sessions, can be used any place in game application code that supports managed records. See gzochi channel, for more information.
Note that the container’s two message delivery services (channels and sessions, described in the previous section) operate independently with respect to message ordering. That is to say, a given session will receive messages in the order they were sent by the server; and a session that belongs to a given channel will receive messages in the order they were sent to the channel. But the container does not establish a global ordering across these two services. So if the server sends two messages to a channel to which some session belongs, and subsequently sends another message directly to that same session, the client may receive those messages in “interleaved” order—meaning, for example, that the client may receive the first channel message, followed by the direct session message, followed by the second channel message, even though that ordering does not correspond to the global sequential order in which the messages were sent.
Next: Transactional logging, Previous: Channel management, Up: Application services [Contents][Index]
gzochid provides an explicit task scheduling API that is used in place of other mechanisms for asynchronous processing, such as threads. A task in gzochid is a callback similar to the ones invoked for application lifecycle events, such as initialization; it specifies a procedure to execute (qualified by a Guile module name), accompanied optionally by an arbitrary managed record to supply contextual information.
The task API provides several options for task scheduling. Scheduling is a transactional operation with respect to the code doing the scheduling, so the earliest a scheduled task can run is following a successful commit of the transactional code (a task or a lifecycle callback) that scheduled it. Additionally, the execution of a task may be delayed by a user-specified number of milliseconds. If a task needs to run more than once, you can reschedule it explicitly as part of its execution, or you can schedule it as a periodic task. Periodic tasks are automatically rescheduled to run on a repeating basis with each execution following the previous one after a specified number of milliseconds.
Task scheduling is persistent, such that the schedule of pending tasks will survive a restart of the gzochid container. See gzochi task, for more information.
Previous: Task scheduling, Up: Application services [Contents][Index]
Because gzochid executes application code transactionally, any side effects (e.g., queued messages, scheduled tasks) will be rolled back if the enclosing transaction fails to commit. And a single task may be executed several times before committing successfully if there is heavy contention for data or other resources. As such, if log messages are written non-transactionally, it can be difficult to use them to trace application events.
The gzochid container provides an API for writing log messages that will only be flushed on a successful commit. For example, if the following segment of transactional code is attempted and rolled back twice before committing:
(display "A log message.") (newline) (gzochi:log-info "A transactional log message.")
...the output might include:
A log message. A log message. A log message. A transactional log message.
Per-application log configuration can be specified via the
logging
element in the application descriptor, which allows
an application developer to specify an optional log priority
threshold for messages from that application, as well as an optional
path to a file for log messages specific to the application. The
default log priority threshold corresponds to messages written at
the info
level. If a log file is not specified, application
messages are written to the container’s default log stream.
<logging threshold="WARNING" file="/path/to/my_app.log" />
See gzochi log, for more information.
Next: User authentication, Previous: Application services, Up: Top [Contents][Index]
The protocol used for gzochi client-server communication is documented below. Note that this description is provided merely for the curious; the responsibility of gzochi game developers is to handle the messages that this underlying protocol delivers—in the form of byte arrays—to the client or server. The format and encoding of these byte arrays is left up to game developers to determine.
The low-level protocol described below conforms to a general pattern of a two-byte prefix encoding the length of the message payload, if any, followed by an “opcode” indicating the purpose of the message. (The opcode byte is not included in the length prefix.) The maximum size of a message body sent between components in a gzochi game is thus 65535. Note that this is not necessarily the size of the packet that delivers the message; the client and server may break a larger protocol up into smaller packets that are re-assembled by the recipient. (Naturally, this behavior is transparent to gzochi game developers.)
The following table lists the message types and structures that make up the low-level gzochi byte protocol.
LOGIN_REQUEST (0x10)
A request from a client to login to a gzochid application endpoint. The message payload must include the UTF-8-encoded endpoint name followed by a null byte (0x0), followed by a byte sequence that will be passed uninterpreted to the authentication plugin configured for the specified endpoint.
LOGIN_SUCCESS (0x11)
A message from the server indicating that a LOGIN_REQUEST
was
received and that authentication was successful. There is no message
payload for this message.
LOGIN_FAILURE (0x12)
A message from the server indicating that a LOGIN_REQUEST
was
received but that the login could not be completed successfully.
LOGOUT_REQUEST (0x20)
A request from an authenticated client to perform a graceful disconnect from a gzochid server. There is no message payload for this message.
LOGOUT_SUCCESS (0x21)
A message from the server indicating that a LOGOUT_REQUEST
message was received. There is no message payload for this message.
Following the dispatch of this message, the server will close the
client’s socket connection.
SESSION_DISCONNECTED (0x30)
A message from the server notifying the client that the server will be immediately closing the client’s socket connection. There is no message payload for this message.
SESSION_MESSAGE (0x31)
A message that may be sent from the server or the client that carries a message to be delivered, respectively, to a gzochid game application endpoint or to client application code. The message payload follows the opcode as a byte sequence that will be passed uninterpreted to a handler registered by game code.
Next: Monitoring, Previous: Communication protocol, Up: Top [Contents][Index]
gzochid employs a pluggable authentication mechanism to allow each
game application endpoint to specify an authentication scheme that
meets its security requirements. Authentication plugins are built
and distributed as shared libraries and should be installed to the
directory specified by “auth.plugin.dir” in the
gzochid.conf file. (By default, this location is the
“gzochid/auth” directory under the library directory of the
installation prefix; for example,
‘/usr/local/lib/gzochid/auth’. This directory is scanned on
server startup and any discovered plugins are dynamically loaded.
Plugins are configured for use by individual game applications via
the auth
element in the game descriptor file. Nested
property
elements can be used pass application-specific
configuration to the plugin like so:
<auth type="password_file"> <property name="path" value="/path/to/passwords.txt" /> </auth>
The plugins currently available are described in the following sections.
• Pass-thru authentication | ||
• Password file authentication | ||
• Kerberos v5 authentication |
Next: Password file authentication, Up: User authentication [Contents][Index]
The pass-thru authentication plugin performs no actual authentication—all login requests are accepted—and the contents of the byte sequence included as part of the login request are interpeted as a UTF-8-encoded text string and are used as the name portion of the identity for the resulting client sessions.
Note that pass-thru authentication is built into the gzochid server container and does not need to be loaded or otherwise configured. This scheme is what will be used by applications that do not explicitly specify another authentication plugin.
Next: Kerberos v5 authentication, Previous: Pass-thru authentication, Up: User authentication [Contents][Index]
The password file authentication plugin allows a game application to authenticate users against a list defined in a text file. The location of this file is given by the required “path” property described below. Each row of the password file gives a username, followed by the equals (“=”) character, followed by a password, and terminated by a newline.
testuser=password123
This plugin interprets the byte string passed by the client during
authentication as follows: All characters up to (but not including)
a NULL byte (0x00
) are interpreted as a username and are used
as the name portion of the identity for the client session following
successful authentication; all characters following this NULL byte
are interpreted as a password and must match the password component
of the row corresponding to the username in the password file.
To use the password file plugin to authenticate clients of an
application, specify “password_file” as the value of the
type
attribute in an auth
element in the application’s
game descriptor file. This plugin accepts the following
configuration properties.
Property | Required? | Description |
---|---|---|
path | true | The absolute path to the password file. |
Previous: Password file authentication, Up: User authentication [Contents][Index]
The Kerberos v5 authentication plugin allows a game application to
act as a host-based service in a Kerberos authentication realm, and
verify tickets generated for a user by a ticket granting server. The
contents of the byte sequence included as part of the login request
are interpeted as an AP-REQ
request and passed to the
krb5_rd_req
Kerberos API function. The name portion of the
identity created for the resulting client session is obtained by
calling the krb5_unparse_name
function on the parsed request
ticket.
See the README file included in the gzochi source
distribution for instructions for building the Kerberos
authentication plugin and linking with the Kerberos libraries. To
use the Kerberos plugin to authenticate the users of an application,
specify “krb5” as the value of the type
attribute in an
auth
element in the application’s game descriptor file. This
plugin accepts the following configuration properties.
Property | Required? | Description |
---|---|---|
service_name | false | The service principal name to use (defaults to “gzochi”). |
keytab_file | false | The absolute path to the keytab file to be used by the service. |
The configured service principal name must match the target service principal for which the client generated the ticket. The following sample code extracts the credentials for the current user principal from a local credentials cache and encodes a request that can be read by the Kerberos authentication plugin. It may be useful as a basis for building clients of applications that use this plugin.
#include <krb5.h> #include <stddef.h> void authenticate () { krb5_auth_context auth_context; krb5_ccache ccache; krb5_context k5_ctx; krb5_data outbuf; krb5_init_context (&k5_ctx); krb5_cc_default (k5_ctx, &ccache); krb5_auth_con_init (k5_ctx, &auth_context); krb5_mk_req (k5_ctx, &auth_context, 0, "gzochi", "localhost.localdomain", NULL, ccache, &outbuf); /* The contents of outbuf.data should be sent as the payload of an authentication request to the gzochid server. */ ... }
Note that because of the one-way nature of the gzochi client
authentication protocol, game applications cannot prove their
identity to an authenticating client, and the
AP_OPTS_MUTUAL_REQUIRED
flag should not be used by clients.
Next: Remote debugging, Previous: User authentication, Up: Top [Contents][Index]
When enabled, the gzochid administrative module collect various bits of statistical information and makes resources related to active game applications available for reporting. The module’s architecture supports the registration of sub-modules—described below—that expose this information in different ways.
The monitoring web server
When the monitoring web server is enabled, it listens for HTTP connections on its configured port (8080 by default) and serves HTML pages with information about the server and the games it runs. In particular, a browser for the game applications’ data stores is provided, which renders the contents of the store in a format similar to a “hex editor” application. There are two ways of accessing the data store through the monitoring web server. The binding list can be accessed by visiting the following URL in a web browser:
http://localhost:8080/app/my-game/names/
Provided the server is running on the local machine and listening on port 8080, the URL above will return a list of bound names in the data store for ‘my-game’, with links to the data bound to each name. For a lower-level view of the store, you can visit the URL:
http://localhost:8080/app/my-game/oids/
...which serves a list of all of the managed records in the store, indexed by the internal object identifiers the container uses to track them.
Future plans for the monitoring web server include per-game statistical reporting on data such as transactional throughput and client session volume.
Next: Application design with gzochid, Previous: Monitoring, Up: Top [Contents][Index]
Application code deployed to gzochid executes in a context with fairly unique characteristics. Even when unit tests—which should be the primary means of detecting and preventing bugs—have been written, situations present themselves in which inspecting the state of a running application is the most effective way to troubleshoot an error or incorrect behavior. To this end, gzochid offers a remote debugging interface that allows a developer to connect to a running gzochid instance using a telnet client and to evaluate Scheme code within the context of an application hosted by that instance.
The debugging server listens on the port specified by the
module.debug.port
setting in the server configuration file.
When a client connects to the debug port, a new Scheme REPL (Read
Evaluate Print Loop) is launched and associated with a fresh
“sandbox” environment in which the (guile)
,
(gzochi)
, and (gzochi admin)
modules have been
pre-loaded. A user can interact with the debugger using the full
complement of Scheme syntax and Guile REPL commands
(e.g., ,pretty-print
). A typical debugging session might have
the form:
julian@navigator:~$ telnet localhost 37146 Trying ::1... Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. GNU Guile 2.0.6.31-2446f Copyright (C) 1995-2012 Free Software Foundation, Inc. Guile comes with ABSOLUTELY NO WARRANTY; for details type `,show w'. This program is free software, and you are welcome to redistribute it under certain conditions; type `,show c' for details. Enter `,help' for help. scheme@(#{ g109}#)> (gzochi:applications) $1 = (#<r6rs:record:gzochi:application-context>) scheme@(#{ g109}#)> (gzochi:with-application (car $1) (lambda () (gzochi:get-binding "scoreboard"))) $2 = #<r6rs:record:my-game:scoreboard>
Next: Scheme API reference, Previous: Remote debugging, Up: Top [Contents][Index]
As the introduction to this manual explains, gzochid is a server container for gzochi game applications. To take full advantage of this architecture, it may be useful to understand the way gzochid interacts with your application.
There are several entry points to a game application. When an application is started for the first time, its initialization procedure is invoked. This procedure is responsible for setting up any global state required by the game and creating any managed record bindings that must be present before client connections can be accepted. The game application descriptor also registers a callback procedure that is invoked when a new, authenticated client connection is established. See the
The following paragraphs are describe some best practices for game programming in gzochid.
Avoid top-level Scheme bindings for application state.
Modifications to data accessed via bindings in the top-level module
environment (i.e., variables created using define
) cannot be
tracked by gzochid, whether the data is part of a managed record or
not. To share mutable state between different execution paths in a
game application, bind the state to a name with the gzochid binding
API and retrieve and modify its value—creating a local definition
via a let
form, perhaps—within the scope of the code that
needs it. There is no need to synchronize access to data managed by
gzochid; the container will prevent race conditions by rolling back
task execution as necessary.
Express concurrency through task scheduling.
Likewise, you should never need to explicitly launch a thread to handle a bit of application work. Scheduling work via gzochid’s task API ensures that it is executed in the proper transactional context and that it will be transparently rescheduled following a non-fatal error and that its status will survive the failure and restart of the container.
Limit task scope.
The longer a task runs and the more resources it accesses, the more
likely it is to disrupt the behavior of other
simultaneously-executing tasks, and the less likely its transaction
will be able to commit successfully—tasks whose execution time
exceeds the configured value of tx.timeout
will be aborted or
otherwise prevented from committing. If there is a lot of work to be
done or there are many objects to be modified, try breaking a large
task into a series of smaller tasks, each of which does a portion of
the work and then schedules the next portion to run as soon as
possible.
Avoid external side effects.
Because application tasks are executed within the scope of a transaction that may be rolled back and re-attempted arbitrarily many times, any side effects these tasks have that are not managed by gzochid may be “played back” multiple times along with the rest of the task’s execution, possibly duplicating their impact.
Next: An example application, Previous: Application design with gzochid, Up: Top [Contents][Index]
The following sections describe the Scheme API exposed to the
server-side components of gzochi applications by the gzochid
container. The API consists of a set of use-specific R6RS libraries
that can be imported independently or all together via the
(gzochi)
composite library.
• gzochi admin | ||
• gzochi app | ||
• gzochi channel | ||
• gzochi client | ||
• gzochi conditions | ||
• gzochi data | ||
• gzochi io | ||
• gzochi log | ||
• gzochi task | ||
• gzochi tx | ||
• gzochi |
Next: gzochi app, Up: Scheme API reference [Contents][Index]
The (gzochi admin)
module exports procedures and data types
that enable introspection of game application state and the
execution of Scheme code within the context of a running game
application.
These functions are intended for use with the remote debugging
interface provided by the gzochid container. Because their use cases
as part of deployed application code are not well defined, they are
not exported from the (gzochi)
composite library.
Returns a list of application context objects representing the set of applications running in the container.
Returns an application context representing the “current”
application, e.g. as set by gzochi:with-application
, or
#f
if the current application has not been set.
Returns #t
if obj is an application context object
(as returned by gzochi:applications
or
gzochi:current-application
, #f
otherwise.
Returns the name of the application represented by the application context object context.
Calls the zero-argument procedure thunk with the current application set temporarily to the application represented by the application context object context. This function returns an unspecified value.
The application that was current before calling
gzochi:with-application
(or #f
if there was none) will
be restored when thunk exits locally or non-locally.
Next: gzochi channel, Previous: gzochi admin, Up: Scheme API reference [Contents][Index]
The (gzochi app)
module exports procedures and data types
common to other components of the API.
Callbacks are serializable references to Scheme procedures with some optionally associated data. They are used in several places to indicate actions to be taken by game application code upon notification of an asynchronous event such as a new autheticated client connection. Procedures are represented within callbacks as Scheme symbols; the modules that export them are represented as lists of symbols. The data associated with a callback—if any—must take the form of a managed record, so that its lifecycle may be managed by the container.
Constructs a new gzochi:callback
with the specified
procedure, module, and optional data.
Expands to an invocation of gzochi:make-callback
with the
specified procedure, module, and optional data. If module
is omitted, the module in which procedure is defined will be
used.
Returns #t
if obj is a gzochi:callback
,
#f
otherwise.
Returns the module specification for the gzochi:callback
callback as a list of symbols.
Returns the procedure name for the gzochi:callback
callback as a symbol.
Returns the managed record that encapsulates the data for the
gzochi:callback
callback or #f
if the callback
was created without any.
The value of this fluid is set to the root deployment directory of the application to which the currently executing code belongs. (By default, if you are installing from the source distribution, this is “/usr/local/share/gzochid/deploy/[application name]”.)
Next: gzochi client, Previous: gzochi app, Up: Scheme API reference [Contents][Index]
The (gzochi channel)
library exports procedures that are
useful for managing client communication channels.
Creates and returns a new channel bound to the string specified by
name. A &gzochi:name-exists
condition will be raised if
there is already a channel with that name.
Returns the channel bound to the specified string name, which
must have been created previously via a call to
gzochi:create-channel
. A &gzochi:name-not-bound
condition will be raised if there is no channel wit
Returns #t
if obj is a channel, #f
otherwise.
Returns the name of the channel channel as a string.
Adds the client session session to the channel channel. session is guaranteed to receive any messages successfully committed following the successful commit of the join operation.
Removes the client session session from the channel channel. session is guaranteed not to receive any messages committed following the successful commit of the leave operation.
Enqueues a message to be sent, in the form of the bytevector msg, to all client sessions that are members of the channel channel at the time this procedure is called.
Destroys the channel channel, unbinding its associated name and removing all constituent client sessions.
Next: gzochi conditions, Previous: gzochi channel, Up: Scheme API reference [Contents][Index]
The (gzochi client)
library provides procedures that are
useful for working with individual client sessions.
See (gzochi channel), for functionality related to groups of
client sessions.
Client sessions are managed records; as such, you may store a
session as the value of fields in other managed records or as the
data for tasks or other callbacks. When a client is disconnected,
the session record is removed from the data store, and attempts to
access it will result in a &gzochi:object-removed
condition
being raised.
Returns #t
if obj is a client session record, #f
otherwise.
Returns the identity associated with the session session as a string. The game’s authentication plugin is responsible for setting the session’s identity.
Enqueues a message to be sent, in the form of the bytevector msg, to the client session session.
Client session listeners are managed records that are used by the
gzochid container to notify game application code of events related
to client sessions. A listener is returned from the callback for the
logged in event to indicate a successful login handshake
(#f
may be returned instead to signal a login failure.) This
listener must in turn be configured with two gzochi:callback
objects, one to be invoked when a new message is received from the
spcified session, the other to be invoked when the session
disconnects from the server.
The record type, for introspection or use as a base type.
Constructs a new gzochi:client-session-listener
with the
specified gzochi:callback
objects received-message and
disconnected, which will be invoked, respectively, when a
message is received from a client and when a client disconnects from
the server.
Returns #t
if obj is a
gzochi:client-session-listener
, #f
otherwise.
Returns the gzochi:callback
registered with the
gzochi:client-session-listener
listener for handling
received messages.
Returns the gzochi:callback
registered with the
gzochi:client-session-listener
listener for handling
session disconnection.
Next: gzochi data, Previous: gzochi client, Up: Scheme API reference [Contents][Index]
The (gzochi conditions)
library exposes condition types and
related procedures for conditions that may be raised during the
execution of game application code.
A &gzochi:name-exists
condition is raised in contexts in
which an attempt is being made to introduce a new binding, but the
target name is already bound.
The condition type, for introspection or use as a base type for other conditions.
Contructs a new &gzochi:name-exists
condition for the
specified name string name.
Returns #t
of obj is a &gzochi:name-exists
condition, #f
otherwise.
Returns the name associated with the specified
&gzochi:name-exists
condition cond.
A &gzochi:name-not-bound
condition is raised in contexts in
which a non-existent named binding is looked up.
The condition type, for introspection or use as a base type for other conditions.
Contructs a new &gzochi:name-not-bound
condition for the
specified name string name.
Returns #t
of obj is a &gzochi:name-not-bound
condition, #f
otherwise.
Returns the name associated with the specified
&gzochi:name-not-bound
condition cond.
A &gzochi:object-removed
condition is raised in contexts
when a reference to a managed record is accessed after the record
has been removed. This may happen for a number of reasons: A client
session record used as a field value or binding and which is
subsequently disconnected; a named binding whose target value is
explicitly removed; or a managed record used as a value that is
accessed after it is has been explicitly removed.
The condition type, for introspection or use as a base type for other conditions.
Contructs a new &gzochi:object-removed
condition.
Returns #t
of obj is a &gzochi:object-removed
condition, #f
otherwise.
A &gzochi:no-current-application
condition is raised when an
operation requiring an application context is attempted and none is
present. This happens most frequently because of user error in an
interactive remote debugging session—for example, a call to a
transaction-aware API function that is not wrapped in an
invocation of gzochi:with-application
.
The condition type, for introspection or use as a base type for other conditions.
Contructs a new &gzochi:no-current-application
condition.
Returns #t
of obj is a &gzochi:no-current-application
condition, #f
otherwise.
A &gzochi:transaction-aborted
condition is raised when a
transactional operation is attempted and the transaction bound to
the current thread has been found to be in an inconsistent
state—that is to say, it has been rolled back or marked for
rollback.
Although this condition will almost aways be raised in a non-continuable way, aborted transactions are an expected part of the control flow of a gzochi game application. A transaction may be aborted for a number of reasons; the code executing as part of the transaction may have accessed data in a way that brought it into conflict with code executing as part of another transaction, or its execution time may have exceeded a configured threshold and the scheduler has pre-emptively aborted its transaction to prevent it from interfering with other transactions.
A &gzochi:transaction-aborted
condition does not typically
require explicit handling. Rather, it is best to allow the code that
triggered the condition to exit non-locally; gzochid’s task
scheduler will observe the state of the transaction upon exit and
reschedule the task or callback accordingly.
The condition type, for introspection or use as a base type for other conditions.
Contructs a new &gzochi:transaction-aborted
condition.
Returns #t
if obj is a &gzochi:transaction-aborted
condition, #f
otherwise.
A &gzochi:transaction-retry
condition is raised when a
transactional operation or an interaction with the gzochi API fails
but is safe to retry and worth retrying because it might succeed on
a subsequent execution attempt. This condition is often raised as a
composite condition with a &gzochi:transaction-timeout
condition when a task’s execution times out. The container’s Scheme
interface recognizes this condition type, and the container uses it
as part of determining whether to reschedule or abandon a task.
Application code may raise this condition explicitly, so as to release resources if it can determine ahead of time that the current transaction will not be able to commit.
The condition type, for introspection or use as a base type for other conditions.
Constructs a new &gzochi:transaction-retry
condition.
Returns #t
if obj is a &gzochi:transaction-retry
condition, #f
otherwise.
A &gzochi:transaction-timeout
condition is raised when a
transactional operation takes longer than the time remaining for the
current transaction to run, or when the current transaction attempts
a transactional operation after its allotted running time has
elapsed.
The condition type, for introspection or use as a base type for other conditions.
Constructs a new &gzochi:transaction-timeout
condition.
Returns #t
if obj is a &gzochi:transaction-timeout
condition, #f
otherwise.
Next: gzochi io, Previous: gzochi conditions, Up: Scheme API reference [Contents][Index]
The (gzochi data)
library exports the data structures and
procedures that make up gzochid’s data storage and serialization
API. As discussed in a previous section, managed records are the
foundation of any game application’s interaction with container’s
data services. The syntactic and procedural APIs for managed records
are discussed below.
Note that because managed records are implemented on top of R6RS
records, they may be treated as such for the purposes of the
procedures in the (rnrs records inspection)
library.
Defines a new managed record type, introducing bindings for a record-type descriptor, a record constructor descriptor, a constructor procedure, a record predicate, and managed accessor and mutator procedures for the new managed record type’s fields.
The structure of name-spec and the record-clause
sub-forms are the same as in the R6RS define-record-type
,
with the following exceptions:
First, each field definition that appears in a fields
declaration of a record-clause
sub-form of a managed record
definition may include a serialization specification that
provides the serialization to use for the field when it is
marshalled to or from persistent storage. This specification has the
form (serialization s)
, where s is a
gzochi:serialization
record. If the serialization
specification is omitted, the container will use a default
serialization that requires all field values to be managed records
themselves or #f
.
A custom serial UID may be specified by adding a serial-uid
clause as one of the record-clause sub-forms. This UID will
be used to register the record type definition in the default type
registry, or in the registry specified by the type-registry
clause, if one is provided. Custom serial UIDs and non-default type
registries are mainly useful when implementing migration processes.
See Migrating data, for more information.
Evaluates to the managed record-type descriptor associated with the type specified by record-name.
Evaluates to the managed record-constructor descriptor associated with the type specified by record-name.
Returns a new record-type descriptor for the managed record with the
specified properties, which have the same semantics as they do when
passed to R6RS’s make-record-type-descriptor
, with the
exceptions that the field descriptors in fields may contain
serialization specifiers as described above, and that the optional
keyword arguments #:serial-uid
and #:type-registry
may
be present and have the effect described above.
Returns the managed record field accessor procedure for the kth field of the managed record-type descriptor mrtd.
Returns the managed record field mutator procedure for the kth
field of the managed record-type descriptor mrtd. An
&assertion
condition will be raised if this field is not
mutable.
Returns a constructor procedure for the managed record constructor
descriptor mrcd. This procedure returns the same value as the
record-constructor
procedure in the
(rnrs records procedural)
library.
These procedures return the same values as their counterparts in the
(rnrs records procedural)
library.
A constructor and type predicate for managed record type registries,
which can be used to control the visibility of managed record types
in different contexts; in particular, across different schemas
during a database migration.
gzochi:make-managed-record-type-registry
constructs a new
type registry object. gzochi:managed-record-type-registry?
returns #t
if obj is a managed record type registry,
#f
otherwise.
Returns #t
if obj is a managed record, #f
otherwise.
Returns the record-type descriptor for mr. This procedure
returns the same value as the record-rtd
procedure in the
(rnrs records procedural)
library.
References to managed records within an object graph of other managed records will be tracked transparently by the container. The following procedures can be used to explicitly store and retrieve references to managed records by name.
Returns the managed record associated with the strig name name.
A &gzochi:name-not-bound
condition will be raised if there is
no binding for name.
Creates an association between the string name and object, which must be a managed record, replacing any previous binding that may exist for name.
Removes the binding for the string name. A
&gzochi:name-not-bound
condition will be raised if there is
no binding for name. Note that this procedure only removes
the association between name and a managed record; to remove
the record itself, you must call gzochi:remove-record!
.
Many of the operations provided by (gzochi data)
are
supported only for managed records. For example, you cannot use
gzochi:set-binding!
to store a primitive value; you must
first “wrap” that value as a field in a managed record type that
includes information about how it should be serialized. In lieu of
creating distinct managed record types to wrap every unmanaged type
that needs to be stored, the procedures below are provided to
support the use of the generic gzochi:managed-serializable
type that encapsulates the serialization information for unmanaged
types.
Constructs a new gzochi:managed-serializable
object with the
specified value. The keyword argument #:serialization gives
a gzochi:serialization-reference
record used for serializing
and deserializing the value.
Returns #t
if obj is a managed serializable record,
#f
otherwise.
Returns the value wrapped by the managed serializable record ms.
Compound data types such as vectors and hash tables are necessary components of any non-trivial application, but designing structures that support highly concurrent access to and modification of their contents is challenging. The following API describes “managed” implementations of R6RS vectors and hash tables, which partition their elements such that conflicts between transactions accessing different elements of the same container are minimized. For applications that require access to sequentially-ordered elements and need the container to resize itself as elements are added or removed, a managed sequence type is provided which features an API based on the SRFI-44 collections proposal (http://srf.schemers.org/srfi-44/srfi-44.html).
Returns a newly allocated managed vector of len elements. The
initial contents of each position is set to #f
.
Returns a newly allocated managed vector composed of the given
arguments. If any vector element is not a managed record, the
keyword argument #:serialization must be given (in the form of
a gzochi:serialization-reference
record), and managed
serializable wrappers will be generated to hold any unmanaged
elements.
Returns #t
if obj is a managed vector, #f
otherwise.
Returns the contents of position k of vec. k must be a valid index of vec.
Stores obj at position k of vec. k must be a
valid index of vec. The value returned by
gzochi:managed-vector-set!
is unspecified. If obj is
not a managed record, the keyword argument #:serialization
must be given (in the form of gzochi:serialization-reference
record), and a managed serializable wrapper will be generated to
hold obj.
Returns the number of elements in vector as an exact integer.
Return a newly allocated list composed of the contents of v.
Constructs a new hash table that uses the procedure specified by the
gzochi:callback
arguments equiv-callback to compare keys
and hash-callback as a hash function. equiv-callback must
specify a procedure that accepts two arguments and returns a true
value if the are equivalent, #f
otherwise, hash-callback
a procedure that accepts one argument and returns a non-negative
integer.
Returns #t
if obj is a managed hash table, #f
otherwise.
Returns the number of keys currently in the managed hash table hashtable.
Returns the value associated with key in the the managed hash table hashtable or default if none is found.
Associates the key key wth the value obj in the managed hash table hashtable, and returns an unspecified value.
If key is not a managed record, the keyword argument
#:key-serialization must be given (in the form of a
gzochi:serialization-reference
record), and a managed
serializable wrapper will be generated to hold key. Likewise,
if obj is not a managed record, #:value-serialization
must be provided.
Removes any association found for the key key in the hash table hashtable, and returns an unspecified value.
Returns #t
if the managed hash table hashtable contains
an association for the key key, #f
otherwise.
Associates with key in the managed hash table hashtable the result of calling proc, which must be a procedure that takes one argument, on the value currently associated with key in hashtable—or on default if no such association exists.
If key is not a managed record, the keyword argument #:key-serialization must be given. If proc returns something that is not a managed record—or if default becomes the value of the association—then #:value-serialization must be provided.
Removes all of the associations from the managed hash table hashtable.
Returns a managed vector of the keys with associations in the managed hash table hashtable, in an unspecified order.
Returns two values—a managed vector of the keys with associations in the managed hash table hashtable, and a managed vector of the values to which these keys are mapped, in corresponding but unspecified order.
Returns a gzochi:callback
record that represents the
equivalence predicate used by hashtable.
Returns a gzochi:callback
record that represents the hash
function used by hashtable
Returns a newly allocated managed sequence.
Returns #t
if obj is a managed sequence, #f
otherwise.
Return a newly allocated list composed of the contents of seq.
Appends obj to the end of seq. The value returned by
gzochi:managed-sequence-add!
is unspecified. If obj is
not a managed record, the keyword argument #:serialization
must be given (in the form of gzochi:serialization-reference
record), and a managed serializable wrapper will be generated to
hold obj.
Returns #t
if seq contains at least one element
equivalent to obj as determined by the equivalence procedure
pred, #f
otherwise. If pred is not specified,
eq?
will be used to check equivalence.
Removes the first element of seq equivalent to obj, as
determined by the equivalence procedure pred. If pred is
not specified eq?
will be use to check equivalence. The
positions of all elements that come after the removed element are
decreased by one. The value returned by
gzochi:managed-sequence-delete!
is unspecified.
Removes the element at position i of seq. The positions
of all elements that come after i are decreased by one. The
value returned by gzochi:managed-sequence-delete-at!
is
unspecified.
Applies fold-fn to each element in seq. fold-fn
should accept a single element from the sequence as its first
argument, and the values passed as seeds as its remaining
arguments. The function must return either #f
, indicating
that the folding iteration should halt; or the values to passed as
seed values on the next invocation of fold-fn. The fold
completes when fold-fn has been applied to every element of
seq, or when fold-fn returns #f
.
gzochi:managed-sequence-fold-left
applies fold-fn to
every element in seq starting with position 0;
gzochi:managed-sequence-fold-right
begins with the element
at the end of the sequence and works backwards to the first.
These procedures return the final set of seed values returned by
fold-fn as the values of the fold.
Inserts obj into seq at position i, which must be
between zero and the nunber of elements currently in the sequence,
inclusive. The positions of all elements that come after i are
increased by one. The value returned by
gzochi:managed-sequence-insert!
is unspecified. If obj
is not a managed record, the keyword argument #:serialization
must be given (in the form of a
gzochi:serialization-reference
record), and a managed
serializable wrapper will be generated to hold obj.
Returns the element at position i in seq.
Replaces the element in seq at position i, which must be
between zero and the number of elements currently in the sequence,
with obj. The value returned by
gzochi:managed-sequence-set!
is unspecified. If obj
is not a managed record, the keyword argument #:serialization
must be given (in the form of a
gzochi:serialization-reference
record), and a managed
serializable wrapper will be generated to hold obj.
Returns the number of entities currently in the managed sequence seq.
Next: gzochi log, Previous: gzochi data, Up: Scheme API reference [Contents][Index]
The (gzochi io)
library exports structures and procedures
useful for building serializations, which are used by the data
management features of the gzochid container for storing and
retrieving application data. The serializations described below may
be used directly as field serializations for a managed record-type
descriptor or composed to form serializations for more complex
types.
A serialization is an R6RS record with fields for a serialization procedure and a deserialization procedure. Serialization procedures should take two arguments, an output port and the value to be serialized, and should write the serialized form of the value to the port as a sequence of bytes; deserialization procedures will be passed an input port and should read the bytes necessary to return a value of the required type.
No type or length information—or delimiters—other than what the serializers themselves add to the output will be persisted. In particular, deserializer procedures should take care not to read more bytes than necessary from the port, as doing so will disrupt the operation of subsequent deserializers.
The base serialization record-type, constructor, predicate, and field accessors.
A subtype of gzochi:serialization
that is itself serializable
in terms of qualified procedure references to serializer and
deserializer procedures.
Expands to an invocation of
gzochi:make-serialization-reference
with the specified
serializer and deserializer procedures and modules. If the module
names are omitted, the module in which serializer-procedure
and deserializer-procedure are defined will be used.
A serialization that uses an efficient, variable-length encoding format to read and write integer values of arbitrary size.
A serialization that encodes boolean values as integers, with
1
representing true
and 0
representing false.
Note that the deserializer procedure will treat any non-zero value
as encoding true
.
A serialization that reads and writes text strings using the UTF-8 encoding, with an integer prefix giving the number of characters in the original string.
A serialization that reads and writes symbols by converting them to
and from strings and delegating to the procedures in
gzochi:string-serialization
.
A serialization that reads and writes R6RS bytevectors as sequences of bytes with encoded integer prefixes giving the lengths of the original vectors.
Returns a new gzochi:serialization
record that can be used as
a serialization for a list of arbitrary length in which every
car of the list has its serialization performed by the
serialization ser.
Returns a new gzochi:serialization
record that can be used as
a serialization for a sparse vector of arbitrary length in which
every non-empty element has its serialization performed by the
serialization ser. The optional fill value (which
defaults to #f
) is used to detect empty indices in the source
vector and to fill in empty indices in the deserialized vector.
Next: gzochi task, Previous: gzochi io, Up: Scheme API reference [Contents][Index]
The (gzochi log)
library provides procedures for
performing application-level message logging, at various levels
of priority. All of the procedures in this library do their
writes transactionally, which means that the messages will only
be persisted (to the console and/or a file on disk, depending
on configuration) if the current transaction commits.
These procedures interpret their “rest” arguments as values
with which to replace escapes in the message string. Formatting
and replacement is done according to the rules of Guile’s
simple-format
procedure.
Writes a transactional message at the priority level priority,
a symbol that must be one of err
, warning
,
notice
, info
, and debug
.
Each of these convenience procedures delegates to gzochi:log
,
passing a respective priority value.
Next: gzochi tx, Previous: gzochi log, Up: Scheme API reference [Contents][Index]
The (gzochi task)
library provides procedures for
creating and scheduling transactional tasks. Tasks are
gzochi:callback
records, and represent blocks of code to be
executed on behalf of a game application. All tasks are executed in
a fully transactional context, meaning that their side effects enjoy
guarantees of atomicity.
Transactionally schedules the gzochi:callback
callback
for future execution. If delay is specified, it must be a
non-negative integer giving the number of milliseconds by which the
execution of callback should be delayed. If period is
also specified, the execution of callback will be executed
repeatedly, and period must be a non-negative integer giving
the number of milliseconds that must elapse between executions of
callback.
If period is specified, this procedure futhermore returns a “perodic task handle,” an opaque managed record that can be used to control the scheduling behavior of the resulting task.
Note that the execution delay of a scheduled task, as well as the period of a periodic task, is “best effort.” The task will not be eligible for execution until at least the specified number of milliseconds have elapsed, but may be delayed further depending on the current state of the server.
Returns #t
if obj is a task handle, #f
otherwise.
Cancels the periodic task associated with the periodic task handle handle. Note that depending on the state of the container’s task execution schedule, the task may still be eligible for execution for a short period of time following its cancellation.
After this procedure returns, handle will be removed from the data store.
Next: gzochi, Previous: gzochi task, Up: Scheme API reference [Contents][Index]
The (gzochi tx)
library provides an SPI for integrating
external transactional processes with the lifecycle of a gzochid
container transaction, such that their transactional semantics can
be synchronized with those of the container. Use this library, for
example, to ensure that a row in a relational database is updated
if and only if a modification to a managed record in a game
application is performed successfully.
The gzochi:transaction-participant
record brokers the
relationship between the gzochid container and an external
transaction by allowing application code to register Scheme handler
functions for the three transaction lifecycle events: prepare
,
commit
, and rollback
. The participant bundles these
handler functions and registers with the container to be notified
as these events occur within the scope of the current transaction.
When the prepare
handler is invoked, the transaction
participant should attempt to ensure that all operations bound to
the current transaction can be committed without error. If this is
possible, prepare
should return #t
; if not—because
of a failure or inconsistency—prepare
should return
#f
to indicate that the entire transaction should be rolled
back. Note that the time spent in the prepare
phase is
counted against the duration of the entire transaction. If all
participants in the transaction prepare successfully but exceed the
maximum transaction duration, the container will roll back the
transaction.
When the commit
handler is invoked, the transaction
participant must make permanent or finalize all changes associated
with the transaction. The commit
handler should not exit
non-locally.
When the rollback
handler is invoked, the transaction
participant must undo all changes associated with the transaction.
The rollback
handler should not exit non-locally.
The possible sequences of handler invocations are:
After commit
or rollback
the transaction is complete.
The participant is no longer bound to it and no further calls will
be made to the handler functions.
The transaction particpant constructor, predicate, and field accessors.
The prepare, commit, and rollback handlers must be “thunks”—procedures that accept no arguments. prepare should return a boolean value.
Joins participant, which must be a
gzochi:transaction-participant
record, to the current gzochid
container transaction.
If this procedure returns locally, then a combination of the participant’s ‘prepare’, ‘commit’, and ‘rollback’ handlers corresponding to the container transaction’s lifecycle transitions will be invoked.
Calling this procedure multiple times with the same participant record within the scope of a single transaction has no effect.
Marks the current gzochid container transaction as “rollback-only”
on behalf of the participant, which must be a
gzochi:transaction-participant
record. All transaction-aware
API functions called subsequent to aborting the transaction will
raise &gzochi:transaction-aborted
, and the transaction will
be rolled back once the current callback execution exits.
Use the optional retryable? argument to indicate the transaction is a candidate for retry. (Whether the transaction is actually rescheduled depends on the disposition of the other participants.)
Calling this procedure with a participant that has not previously
been joined to the current container transaction (via
gzochi:join-transaction
) will cause an error to be raised.
Previous: gzochi tx, Up: Scheme API reference [Contents][Index]
The (gzochi)
library is a composite of all of the other
public gzochi libraries, with the exception of ‘(gzochi admin)’ and
‘(gzochi tx)’. It imports and re-exports all of their exported
procedures and syntactic forms.
Next: Database tools, Previous: Scheme API reference, Up: Top [Contents][Index]
The following sample code is a complete gzochid application that implements a simple “Hello, world!” behavior. Authenticated clients receive a simple greeting and may send a single message to the server before being disconnected.
#!r6rs (library (gzochi example hello-world) (export initialize-hello-world hello-client client-message client-disconnected) (import (gzochi) (rnrs)) (define (initialize-hello-world properties) (gzochi:notice "Hello, world!")) (define (hello-client session) (gzochi:client-session-send session (string->utf8 (string-append "Hello, " (gzochi:client-session-name session) "!"))) (gzochi:make-client-session-listener (gzochi:make-callback 'client-message '(gzochi example hello-world)) (gzochi:make-callback 'client-disconnected '(gzochi example hello-world)))) (define (client-message session msg) (let ((name (gzochi:client-session-name session))) (gzochi:notice "~a: ~a" name (utf8->string msg)) (gzochi:client-session-send session (string->utf8 (simple-format "Goodbye, ~a!" name)))) (gzochi:disconnect session)) (define (client-disconnected session) (if #f #f)) )
To launch this application, copy and paste the code above into a file named “hello-world.scm” and copy it to a sub-directory named “hello-world/gzochi/example” below your gzochid application deployment root directory. For example, the default deployment root is “/usr/local/share/gzochid/deploy,” so the full path in the default case would be “/usr/local/share/gzochid/deploy/hello-world/gzochi/example/hello-world.scm.” You’ll also need to copy and paste the following game descriptor XML to a file named “game.xml” in the top-level “hello-world” directory.
<?xml version="1.0"?> <game name="hello-world"> <description>Hello, world!</description> <load-paths /> <initialized> <callback module="gzochi example hello-world" procedure="initialize-hello-world" /> </initialized> <logged-in> <callback module="gzochi example hello-world" procedure="hello-client" /> </logged-in> </game>
The descriptor begins by giving the application’s short name and a
description. The load-paths
element is a list of additional
locations the system should look for Scheme modules when module
dependencies are being resolved. The application’s deployment
directory is implicitly on the load path, so this element is empty
in the descriptor for “hello-world.”
Next there are declarations for the two primary callbacks,
initialization and client connection. This game descriptor file sets
the initialization callback to the initialize-hello-world
procedure in the module (gzochi example hello-world)
and sets
the connection callback to the procedure hello-client
in that
same module.
The Scheme implementation of initialize-hello-world
gets
passed a hash table with the game properties (which will be empty in
this case) and doesn’t do anything except log a message. The
hello-client
callback is a bit more interesting—it sends a
greeting to the newly-connected session and then constructs and
returns a new client session listener, which includes two additional
callbacks that will be used to handle events related specifically to
the new session. The first listener callback will be called when a
message is received from a client session; in this case, the
client-message
procedure is used. The other callback
(client-disconneted
in the example above) will be called when
the session disconnects.
Next: GNU Free Documentation License, Previous: An example application, Up: Top [Contents][Index]
The data for a gzochi game application is stored in three separate databases: Names, which binds names to object identifiers; oids, which stores the serialized objects that encode the state of the game; and meta, which tracks necessary metadata about the object identifier key space. Tools for manipulating these databases are described in the following sections.
• Exporting data | ||
• Importing data | ||
• Migrating data |
Next: Importing data, Up: Database tools [Contents][Index]
Making a copy of a gzochi game application database is a wise thing to do. Although the transactional storage layer of the server works to prevent inconsistent writes and catastrophic loss of data, it is unable to guard against “application-level” corruption of data resulting from software bugs. Creating a backup at strategic intervals lets you create rollback points before major releases, and also gives you access to “scratch” versions of real game state that you can use to test new or experimental code. Copying the contents of the game databases is not as simple as copying the game’s data directory. The files and formats used to store game data are not compatible across storage engine implementations, and so a game database created by an instance of gzochid compiled against one storage engine is not readable by an instance of gzochid compiled against another. And depending on the capabilities of the storage engine, the contents of the files on disk may not fully capture the transactional state of a database being modified by a running gzochid server, nor provide enough context for an independent server instance to successfully reconstruct it.
The gzochi-dump utility allows you to perform a transactional export of the contents of any (or all) of the gzochi game databases in a portable, storage engine-independent format.
To export all of the three databases for a game application (“names,” “oids,” and “meta”), run the gzochi-dump command on the data directory of the target game application, or just give it the name of the application, and it will “guess” its data directory based on the server’s storage configuration.
gzochi-dump /usr/local/var/gzochid/data/mygame
The files names.dump, oids.dump, and meta.dump will be created in the current directory. To export a single game database, qualify the target (path or application name) with the name of the database.
gzochi-dump /usr/local/var/gzochid/data/mygame:names
The contents of the specified database will be written to standard output. See the gzochi-dump man page for more information on the flags understood by the tool.
The gzochi-dump utility produces output in a portable flat-text format very similar to the format used by the db_dump tool distributed with Berkeley DB—though the output of gzochi-dump is the same no matter what storage engine the server is built against. The format is as follows:
First, a header section is written, as a series of name=value pairs. The first line is always the version specifier, “VERSION=N”, where N is the version of the dump output format. The header section ends with the line “HEADER=END”.
Next, a data section is written, as a series of pairs of keys and
values, with each key and each value on its own line. Each line
begins with one character of whitespace, followed by the hexadecimal
representation of the bytes of the key or value in a two-character,
zero-padded format—effectively the output of printf
with
the %.2hhx
conversion specifier. The data section ends with
the line “DATA=END”.
Here is some example output written as the result of dumping the “names” database of one of the example games included in the gzochi source distribution:
VERSION=3 format=bytevalue type=btree HEADER=END 6f2e6d617a6500 33643000 732e6368616e6e656c2e6d61696e00 33643100 732e696e697469616c697a657200 3000 DATA=END
Next: Migrating data, Previous: Exporting data, Up: Database tools [Contents][Index]
The gzochi-load utility rebuilds a game database from the contents of a database dumped by gzochi-dump in the format described in the previous section, read from standard input.
As with gzochi-dump, you may specify the target of the
gzochi-load command as either a path to an application’s data
directory, or as the name of an application hosted by the gzochid
container. The target database must also be given; gzochi-load can
only load data into a single database at a time. The database will
be created if it does not already exist—gzochi-load will complain
and halt if the target database does exist, since its
majority use case is rebuilding a complete database from scratch.
This behavior can be overridden by running gzochi-load with
the --force
argument, but note that doing so runs the risk of
corrupting existing game state, and so this option should be used
with care.
The following example shows how to use the gzochi-load command to rebuild the “oids” database for the application “mygame2.” (gzochi-load will resolve the data directory for this application by reading the server configuration file and scanning the application deployment directory.)
cat oids.dump | gzochi-load mygame2:oids
To make a complete clone of a game application’s databases, dump all three databases transactionally with gzochi-dump, then load each dump file into the corresponding database in the target application data directory as per the shell script included below.
#!/bin/sh DUMPDIR=`mktemp -d` gzochi-dump -o $DUMPDIR $1 gzochi-load $2:meta < $DUMPDIR/meta.dump \ && gzochi-load $2:oids < $DUMPDIR/oids.dump \ && gzochi-load $2:names < $DUMPDIR/names.dump rm -rf $DUMPDIR
Previous: Importing data, Up: Database tools [Contents][Index]
It is unusual that a single version of a piece of software serves its users for the duration of their experience of it. Game application software is no different; delivering bug-fixes, performance enhancements, and new features may require that you deploy updates to the game application code running in the gzochid container. Sometimes, this new code may depend on additions or modifications to the structure of the records that describe the persistent state of the game; and these modifications will usually need to be applied to instances of these records that have already been stored in the database.
The gzochi-migrate utility allows you to transform the contents of a gzochi game application database by visiting and optionally re-writing (or removing parts of) the persisted game object graph. A migration to be executed by gzochi-migrate is described in an XML migration descriptor file; the logic for processing the objects in the database being migrated is defined by the migration visitor, a procedure written in Guile Scheme.
gzochi-migrate reads the descriptor, resolves the type registries
and visitor procedure, and then traverses the application-visible
keyspace of the names
database (that is, the set of keys that
begin with “o.”) to identify the roots of the game object graph
and enqueue them for visiting. For each object identifier in the
migrator’s queue, the corresponding record is deserialized from the
oids
database and passed as an argument to the visitor
procedure. Any references to other managed records held by the
record being migrated are enqueued for subsequent visit. In this
way, every managed record that is reachable from a named binding is
visited exactly once.
• The migration visitor procedure | ||
• The migration descriptor | ||
• A sample migration |
Next: The migration descriptor, Up: Migrating data [Contents][Index]
The migration visitor is a Scheme procedure invoked by gzochi-migrate on each reachable object in the game database. It is the responsibility of the migration visitor procedure to provide a disposition for each object it is given, and it does so via the value it returns. If the visitor returns:
#f
the object is removed from the object graph.
a managed record
the object is replaced by this value.
an unspecified value
the object is left unmodified.
any other value
an error is signaled.
The simplest implementation of a migration visitor is a bijective
function over managed records: A procedure that accepts a managed
record and returns one of the values listed above. Visitor
procedures may have side effects, too, though, and may achieve them
through interactions with the object graph or explicit calls to the
data management functions. A visitor procedure may call
gzochi:remove-object!
on its argument or on a record to which
its argument has a field reference. However, named bindings should
not be modified while a migration is in progress; in particular, the
effect of gzochi:set-binding!
is undefined. gzochi API
functionality related to network communication and task scheduling
is not supported during migrations, and the effects of calling
procedures in those modules is also undefined.
Next: A sample migration, Previous: The migration visitor procedure, Up: Migrating data [Contents][Index]
The XML migration descriptor has the schema described in the following paragraphs. A DTD that can be used to validate the structure of your migration descriptors is included in the gzochid source distribution.
The migration descriptor’s document element is migration
, and
it must include a target
attribute giving the name of the
deployed game application to be migrated. (The directories
containing the game database files are computed using the
gzochid.conf file and the conventions of the storage engine
specified in that file via the “storage.engine” setting.)
The input-registry
and output-registry
elements
specify managed record type registries to push onto the type
resolution stack when an object in the graph is deserialized and
when it is serialized, respectively. Controlling the behavior of
type resolution during these lifecycle phases is crucial to
implementing a migration that mutates the structure of a declared
type while preserving its name. Both of these migration descriptor
elements require a module
attribute, which gives the name (as
a whitespace-delimited list of symbols) of the module exporting the
type registry; and the name
attribute, which gives the name
of the type registry within that module. Type registries may be
defined using the gzochi:make-managed-record-type-registry
procedure in the (gzochi data)
API.
The callback
element gives the procedure name and module (via
the procedure
and module
attributes, respectively) of
the migration visitor procedure.
The load-paths
element is a wrapper element for zero or more
load-path
element, each of which specifies as its content an
absolute or relative path to be added to the default Guile
%load-path
variable when the input and output type registries
are resolved, and when the visitor procedure is resolved. Note that
the game application root directory is added to the load path by
default.
Previous: The migration descriptor, Up: Migrating data [Contents][Index]
The following migration adds a new mutable field named
“experience-points” to the managed record with the serial UID
player
. The version of the player structure persisted to the
database without the new field is captured by the managed record
type definition for from-player
below, which registers the
type in the input-registry
type registry. The version of the
player structure that includes the new field and will be serialized
back to the database is declared as to-player
and is
registered in the output-registry
type registry.
After running this migration, the server administrator would need to
deploy a new version of the “my-game” application that includes a
type definition for the player structure that includes the field
added by to-player
.
Note that while the re-use of the player
UID allows this
migration to modify the structure of a record without changing its
type this migration is not strictly idempotent. Re-executing the
migration on an already-migrated database will cause serialized form
of the version of the player structure that includes the
“experience-points” field to be deserialized “into” a version of
the player structure that does not contain that field. The gzochi
object system cannot natively detect this kind of serialization
mismatch; its outcome depends on the behavior of the serializer and
deserializer procedures.
Here is the Scheme module containing the type registries and visitor procedure:
#!r6rs (library (my-game migration player) (export input-registry output-registry migrate-player) (import (gzochi) (rnrs)) (define input-registry (gzochi:make-managed-record-type-registry)) (define output-registry (gzochi:make-managed-record-type-registry)) (gzochi:define-managed-record-type from-player (fields (immutable name (serialization gzochi:string-serialization)) (mutable hit-points (serialization gzochi:integer-serialization))) (serial-uid player) (type-registry input-registry) (protocol (lambda (p) (lambda (name) (p name 0))))) (gzochi:define-managed-record-type to-player (fields (immutable name (serialization gzochi:string-serialization)) (mutable hit-points (serialization gzochi:integer-serialization)) (mutable experience-points (serialization gzochi:integer-serialization))) (serial-uid player) (type-registry output-registry) (protocol (lambda (p) (lambda (name hit-points) (p name hit-points 0))))) (define (migrate-player obj) (if (from-player? obj) (make-to-player (from-player-name obj) (from-player-hit-points obj)))) )
The migration descriptor for this migration:
<?xml version="1.0"?> <migration target="my-game"> <input-registry module="my-game migration player" name="input-registry" /> <output-registry module="my-game migration player" name="output-registry" /> <callback module="my-game migration player" procedure="migrate-player" /> </migration>
Next: Concept index, Previous: Database tools, Up: Top [Contents][Index]
Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc. http://fsf.org/ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.
This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.
We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.
This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.
A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.
A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.
The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.
The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.
A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.
Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.
The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.
The “publisher” means any person or entity that distributes copies of the Document to the public.
A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.
The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.
You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.
You may also lend copies, under the same conditions stated above, and you may publicly display copies.
If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.
If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.
It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.
You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:
If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.
You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.
The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.
You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.
The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.
In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements.”
You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.
You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.
A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.
If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.
Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.
If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.
You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.
The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http://www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Document.
“Massive Multiauthor Collaboration Site” (or “MMC Site”) means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A “Massive Multiauthor Collaboration” (or “MMC”) contained in the site means any set of copyrightable works thus published on the MMC site.
“CC-BY-SA” means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.
“Incorporate” means to publish or republish a Document, in whole or in part, as part of another Document.
An MMC is “eligible for relicensing” if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.
The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.
To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:
Copyright (C) year your name. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.3 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled ``GNU Free Documentation License''.
If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with…Texts.” line with this:
with the Invariant Sections being list their titles, with the Front-Cover Texts being list, and with the Back-Cover Texts being list.
If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.
If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
Next: Procedure index, Previous: GNU Free Documentation License, Up: Top [Contents][Index]
Jump to: | A C D E G I L M P S T |
---|
Jump to: | A C D E G I L M P S T |
---|
Next: Type index, Previous: Concept index, Up: Top [Contents][Index]
Jump to: | &
G |
---|
Jump to: | &
G |
---|
Previous: Procedure index, Up: Top [Contents][Index]
Jump to: | C M S T |
---|
Jump to: | C M S T |
---|