Module programming guidelines

From Adventure Game Studio | Wiki
Jump to navigationJump to search

This document is based on RickJ's programming guidelines document, with input from SSH and Scorpiorus.

A short summary of this document can be found on the forums.


Introduction

A new feature introduced in AGS V2.7 allows reuseable script modules to be created, imported, and exported. This feature makes it possible for multiple authors to create and publish modules that can be used in other games/applications. In this situation it is then necessary to ensure that modules from multiple authors are able to be used together in the same AGS game/application. This document presents guidelines that attempt to achieve this goal.

Naming Conventions

Modules are comprised of a Module Script and a Module Header. The Module Header is included ahead of the normal AGS Script Header in the Global Script and in each Room Script. It is also included ahead of the Module Header of subsequently imported modules.

Names defined in a Module Header cannot be redefined in the game or in subsequently imported modules. For example, if two modules contained a statement such as "#define DOOR ..." in their Module Headers they could not be used in the same game. Also neither module could be used in a game where "DOOR" was already defined.

So how can this situation be be avoided? Let's begin with the module's name. Since it's not possible to use two modules that have the same name in the same game, modules should be given unique names. It has been suggested that modules should be announced and debugged in the Technical Forum, then moved to the Technical Archives. So as of this writing, to get a unique module name one would search the AGS forums (Technical Forum and Technical Archive) for module announcements to see if the prospective module module name has already been used. Perhaps this will evolve over time and become more formalized and/or automated. After obtaining a unique module name, it can then be used to guarantee uniqueness of all other names having global scope as outlined below.

Name Formats

Several notation formats will be used in forming names. These are defined as follows:

Camel Notation 
mixed case characters, the first character of

each word is in uppercase characters and the remaining characters are lowercase.

Uppercase Notation 
all uppercase characters, underscore

characters are used to optionally separate words.

Lowercase Notation 
all lowercase characters, underscore

characters are used to optionally separate words.


Module Name

Modules are to be given unique names, consisting of 3 to 16 characters in Camel Notation. There should be at least three characters to get optimal use of the Script Editor's auto-complete feature. More than 16 characters makes using the module overly burdensome.

   // Module Name
   MyModule

Macro Names

The #define directive is used to create macro or constant definitions. Macro names are to be in Uppercase Notation. Macros defined in the Module Header have global scope so they are to be prefixed with the module name as shown below. There are no restrictions on the number of characters in a macro name.

   // Module Script
   #define MY_MACRO 42

   // Module Header
   #define MyModule_MY_MACRO 42

Enumerated Data Type Names

Enumerated data types are defined in the Module Header and have global scope. { At the time of this writing, it is not clear to the author what scope or other characteristics enums defined in the Module Script have } The enumerated types name is formed using Camel Notation prefixed by the module name. The enum value names are formed using Camel Notation prefixed by the module name and a lowercase "e" as shown below.

   // Enumerated Type
   enum MyModule_MyEnum {
      MyModule_eValue1,
      MyModule_eValue2,
      MyModule_eValueN
   }

Variable Names

Variable name notation is determined by variable scope. There are three possibilities Dynamic, Static, and Global, as described below.

Dynamic Variables - Dynamic variables are defined within the bounds of a function and are in scope only within the bounds of that function. Dynamic Variables are named using Lowercase Notation.

  // Dynamic Variable
  int my_variable;

Static Variables - Static variables are defined outside the bounds of any function and have scope within all functions defined in the module. Static variables are named using Camel Notation.

  // Static Variable
  int MyVariable;

Global Variables - Global variables are static variables that have been exported in the Module Script and imported in the Module Header. Global variables have, as one would expect, have global scope. Global variables are named using Camel Notation prefixed with the module name.

  // Global Variable
  int MyModule_MyVariable;

Function Names

Function name notation is determined by function scope. Strictly speaking functions can be local or global in scope. However, AGS does not permit forward references, so in practice local functions are often categorized as being either a utility function or an application function. The intent of these groupings is to organize the Module Script so that forward reference problems do not arise.

Utility Function - Utility functions have local scope and by convention may be called by other utility functions or application functions. Their names are formed using Lowercase Notation.

   // Utility Function
   function my_function() {
   }

Application Function - Application functions also have local scope but by convention may not be called by other application functions or utility functions.

   // Application Function
   function MyFunction() {
   }

Global Function - Global functions, as one would expect, have global scope and may be called from the Global Script or any Room Script. Global function names are formed using Camel Notation prefixed with the module name.

   // Global Function
   function MyModule_MyFunction() {
   }

An alternative to using global functions is to use static member functions which are discussed below. One advantage is that static member functions are recognized but the auto-complete feature of the Script Editor.


Structures Defined in the Module Script

Structures are created using the "struct" keyword in either the Module Script or in the Module Header. Structures defined in the Module Script have local scope and may contain only member variables.

Structure Name - The names of structures, defined within the Module Script, are to be formed using Lowercase Notation.

Member Variables - The names of member variables, of structures defined with the module script, are to be formed using Camel Notation.

   // Module Script Structure Definition
   struct mystruct {
      int    MyInt;
      char   MyChar;   
      short  MyShort;
      float  MyFloat;    
      String MyString;  // see note below
   }

Instances of structures are to be named according to the above rules governing the naming of static and dynamic variables. Instances of structures are commonly created outside the bounds of any function and so are static and are named using Camel Notation.

   // Module Script Structure Instance
   mystruct MyStruct;

   // Accessing member variables of MyStruct
   MyStruct.MyInt = 42;
   MyStruct.MyChar = 42;
   MyStruct.MyShort = 42;
   MyStruct.MyFloat = 42.0;
   StrCopy(MyStruct.MyString, "42");

Structures Defined in the Module Header

Structures defined in the Module Script have global scope and may contain both member variables and member functions. Such structures are similar to C++ objects (not to be confused with AGS room objects) in form and function.

Normally there will be a "main" structure, defined in the Module Header, having the same name as the module itself. This structure's name is, of course, in Camel Notation as is the module name. The names of public members are to be in Camel Notation while the names of private members are to be in Lowercase Notation as shown below.

   // Main Structure
   struct MyModule {
      // Public Member Variables
      int MyVar;
      writeprotected int MyReadOnlyVar;

      // Public Member Functions
      import function MyFunc();

      // Public Static Member Functions
      import static function MyStaticFunc();
       
      // Private Member Variables
      protected int my_var;  

      // Private Member Functions
      import protected function my_func();
   }

The distinction between public and private members is that public members are globally accessible while private members are accessible only by other members of the structure.

Static member functions have the same characteristics as global functions. They are accessed via the definition of structure rather than an instance of the structure.

   // Global function call
   MyModule_MyFunc();

   // Equivalent using static functions   
   MyModule.MyStaticFunc();

The latter is the preferred method of providing global access to functions as it is more consistent with the new AGS scripting language. As of this writing, static member variables are not supported so this methodology is not a possible alternative to global variables.

Additional structures defined within the Module Header, are to be named using Camel Notation prefixed with the module name. Member variables and member functions are to be named according to the above rules.

   // Main Structure
   struct MyModule_AnotherStruct {
      :
   }

Instances of structures defined in the Module Header may be created in the Module Script, Global Script or any Room Script. Such instances are to be named according to the above rules governing variable names.


Managing Module Dependancies

First of all, modules should be as self-contained and independent as possible. However there are times when it is appropriate for one module to use functions defined in an other module. Such a module is said to be dependent on the other module.

For example, suppose someone is creating a module that will need to do extensive database operations to achieve it's purpose. If a general purpose database module is available it's probably easier and better to just use that module rather than replicating the required database functionality. On the other hand, if only a small portion of the other module is needed, then it would be better to just copy what is required into the new module and not use the the other module directly. Note: Please remember to give credit where credit is due. Document any such copying in your script comments. A statement such as "Derived from IniFile by RickJ" should be adequate.

If a module is dependent on another module and that module hasn't been imported into AGS prior to the dependent module, compiler errors will be generated. It will not necessarily be obvious why these errors are occurring. In addition, the dependent module may require a specific version of a module. So a method of explicitly checking for the dependency and generating an appropriate error message is clearly required. This can be done by defining and checking macro definitions in the Module Header as described below.


Module Versions

Each module is required to have a version number with two decimal places. Version "100" and "1.00" are equivalent, so that integers may be used to hold the version number.

Each module is also required to have one macro definition named ModuleName_VERSION defined as the current version number. Each module is also required to have one macro definition, for each released version of the module, named ModuleName_VERSION_XXX, where XXX is the release version number. An example of this is shown below.

   // Version Macro Definitions
   #define MyModule_VERSION 200
   #define MyModule_VERSION_200
   #define MyModule_VERSION_150
   #define MyModule_VERSION_102
   #define MyModule_VERSION_101
   #define MyModule_VERSION_100

In this example the current version is V2.00 and the previously released versions are V1.50, V1.02, V1.01, V1.00.


Checking Module Dependencies

Now suppose the module MyModule uses functions defined in YourModule Version 2.01. The following directives would be put in MyModule's Module Header.

   // Check if module has been imported into AGS
   #ifndef YourModule_VERSION
   #error *** Err-MyModule, can't find required module "YourModule"!
   #endif

   // if the module is here but the version is too old
   #ifndef YourModule_VERSION_102
   #error *** Err-MyModule, YourModule V1.02 or higher required!
   #ifndef

It should be noted that a new version of a module should also support functionalities of previous versions as well, i.e. the exposed but obsolete functionality should not be removed in order to ensure all dependent modules would work correctly. Such functionality can however be hidden from the autocomplete feature so that the chance it will be used in future would be minimal, making modules backward compatible.

Documentation

A module is not very useful if there is no documentation explaining what it does and how to use it. The documentation should be provided in a plain text file and/or an HTML file. These files should have the same name as the module and with txt and html file extensions as follows:

   // Documentation Files
   MyModule.html
   MyModule.txt

These files may be manually prepared or programmatically generated by extracting comments from the Module Script and Module Header and include the following sections. {note such a document generator is currently in development}

Title

This section contains information about the module. At minimum this should include the following items:

  • Module Name
  • Module Version
  • Authors' Names
  • Dependency List


Abstract

This section should be about one paragraph long and clearly describe the module's functionality.


Contents

This section consists of a table of contents.


Description

This section should tell the basic theory about the module, what it does, how it does it, how to use it, working examples, and perhaps a brief tutorial. Subsections may be freely added to this section as needed.


Reference

This section documents the module's application interface. It is comprised of a completed list of public/global functions, properties, macros/constants and variables.

Definitions 
#define constants, enum values, and data types defined in header
Globals
global variables and global functions
Structure1 
public member variables, public member functions, and static member functions
StructureN 
public member variables, public member functions, and static member functions

Revision History

This section contains a revision history and is to be in chronological order, where each entry is contained on a single line. It is to consist of a date, author's name or forum name, and a brief description of the change, as shown below.

   // Revision History: 
   // 12-20-04 RickJ   created original version
   // 02-01-04 RickJ   V1.00 beta-1 release


License

This section details the terms and conditions under which the module may be published and/or distributed. It also contains a copyright notice.

Publishing

To publish a module a procedure the following items must be completed.

Review Module Manager Settings

Before exporting and publishing a module the Module Manager settings are to be reviewed to verify they have the correct information.

Module Name 
This field contains the module's name in Camel Notation as always.
Author 
This field contains the name of the author. The author name is to be in the form of "First Last (ForumName)".
Version 
This field contains the current version number.
Description 
This field contains a brief description of what the module does without giving too many details.
Edit Module Information 
Set this check box to grant permission to modify the above module info fields.
Edit Script and Header 
Set this check box to grant permission to modify the Module Script and Module Header.

Create Distribution File

A module distribution is comprised of the files listed below. These files are compressed into a single zip archive for distribution.

MyModule.scm
Exported module file
MyModule.txt
Plain text document
MyModule.html 
Optional html document
DemoGame.zip
Optional demo game

The name of the zip archive is formed by appending the version number to the module name. Beta versions are identified by appending the beta version number to the end of the version number as shown in the example below.

   // MyModule Version 1.00 Distribution File
   MyModule_V100.zip

   // MyModule Version 1.00 Beta-3 Distribution File
   MyModule_V100B03.zip


Announce and Review

The distribution file is uploaded to the WWW from where it can be downloaded. The module is announced in the AGS Technical Forum and forum members are invited to comment on and/or test the module. When this process is complete a final release distribution file is created and published on the www.


Licensing

Modules created for the exclusive use of the module's author shouldn't be published or distributed because the source code and executable are one and the same in a script module.

Modules created and published for use by the AGS community are to be distributed under the terms of the LGPL open source license. This license allows the module to be used in an AGS game without requiring the game itself to be open sourced. It also gives permission for the module to be redistributed by others (i.e. mirrored on other websites). To use the LGPL just include the following notice in the Module Script, Module Header, and Document.

  // License
  // 
  // This program is free software; you can redistribute it and/or
  // modify it under the terms of the GNU General Public License
  // as published by the Free Software Foundation; either version 2
  // of the License, or (at your option) any later version.
  // 
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  // 
  // You should have received a copy of the GNU General Public 
  // License along with this program; if not, write to the Free 
  // Software Foundation, Inc., 59 Temple Place - Suite 330, 
  // Boston, MA  02111-1307, USA.
  //
  // Copyright (C) yyyy  name of author
  //---------------------------------------------------------------

The complete LGPL can be found at www.gnu.org/licenses/gpl.html.


Script Module Templates

Templates for Module Script and Module Headers, in the form of plain text files will be made available. These files can be cut and pasted into newly created modules. Perhaps at some point in the future there will be a way of integrating such templates into the "New Module" command.