TODO LiXizhi: Add a formal NPL guide for the 2008 edition here. The following is converted by a doc I wrote in 2005. It is quite outdate.

Neural Parallel Language

SDK and Programming Guide

by

LiXizhi

Web site www.lixizhi.net
Email lxz1982@hotmail.com
Phone 86-0755-86022734
Address Room A411
Shenzhen Tech-Innovation International
Shenzhen, 518057, P.R. China

©Xizhi Li, 2005

All rights reserved

Neural Parallel Language Copyright Notice

Neural Parallel Language or NPL is the intellectual property of ParaEngine Tech Studio and the author and can not be modified and/or redistributed without the permission of the author. The code and documentation of NPL , as provided in this literature, is also copyrighted as above. Examples of code, pictures, and tables relating to NPL , which appear in this literature, may be reproduced for any purposes. The author is grateful if he can be informed of its usage. For distribution of other materials, please contact the author first.

Abstract

Introduction to NPL

Feature Introduction

Neural Parallel Language (or NPL) is an extension programming language which separates code logics from its hardware network environment. It is a new programming standard designed to support (1) frequent code updates, (2) complex graphic user interface and human-computer interactions, and (3) reconfigurable code deployment in a distributed network environment. Its extension interface and basic syntax is based on LUA [x], a popular light-weighted extensible language. It offers good support for object-oriented programming, functional programming, data-driven programming, and most importantly, network-transparent programming (which will be explained later).

One good example of using NPL is coding both client and server logics for on-line computer games. Currently NPL is developed together with ParaEngine [x], a powerful 3D distributed game engine. The language is also being used in the development of a massively multiplayer on-line game based on ParaEngine.

The reason of using NPL, instead of coding directly in C/C++ or another scripting language, takes some time to explain (please refer to the background introduction section). The simple reason is that NPL have several built-in features which are not found in other languages.

Feature 1: Network-transparent programming

"Network-transparent" means that the source code of NPL does not concern with the topology of the actual networked hardware environment. NPL separates code logics from its hardware network environment. In other words, the same source code (files) can be deployed to run on one computer or a dozen of networked computers. The source files are still editable and configurable after a successful deployment, which means that one can continue apply changes to the source code or even splitting files to run on more machines.

Feature 2: Native language support for distributed visualization

Traditionally, distributed system has one or several mirrored visualization or graphic user interface (GUI) terminals. NPL proposes a new computing paradigm, which allows visualizations as well as user interactions to occur on any machine of the distributed system concurrently. Each specialized view will be automatically computed using the same source code.

Feature 3: Distributed namespace architecture support

In NPL, files can be logically organized in namespaces. Each NPL runtimes maintains a dynamic mapping from namespaces to physical address (IP address); hence by modifying this mapping, files can have different physical configurations on the actual hardware. However, name collision is inevitable for distributed systems. One reason is that there is no central boss directing all computers on the network; instead computers are either on their own or working in clusters. Another reason is that the same source code may be instanced (duplicated) thousands of times on a shared network (e.g. the Internet). NPL's solution to the namespace mapping problem is that it maintains a dynamic mapping on a task basis. In other words, a namespace mapping is only valid for some task. Here, task means job done cooperatively by a small group of computers at certain time. Hence, by imposing a few simple rules for programmers, we can completely eliminate namespace mapping problems.

NPL Software Package

There are two distributions for NPL. One is called ParaEngine, a standalone game engine executable with rich 2D and 3D visualization support. It could compile and run NPL source codes with extra support for ParaEngine's 2D and 3D programming API. It is usually sufficient to write fairly complex applications using this first distribution. In fact, we are writing our own distributed multiplayer on-line 3D game with it (for both client and server logics).

Another distribution is an NPL library which can be embedded in C++ host applications, so that the host application will be able to extend NPL language in their own favor.

NPL aims to be an open standard. Its architecture is already discussed in several papers. We will make our implementation free software when it is proved to be useful in its community. However, because the first distribution is tightly integrated with a powerful 3D computer game engine, which involves huge intellectual property, we can not make them both free at the moment.

This reference manual aims to be both concise and complete. ParaEngine's 2D and 3D programming API is fully documented in the NPL Reference book [x]. One can find plenty other papers or documentations on NPL in the reference section.

NPL Language Reference

Language Syntax

The basic syntax is based on LUA, a popular light-weighted extensible language. If you are familiar with LUA, please skip this section.

Reserved identifiers and comments

The following keywords are reserved and cannot be used as identifiers:

And break do else elseif

end false for function if

in local nil not or

repeat return then true until while

_X... (where X = any uppercase letter)

-- Comment to end of line.

--... Multi-line comment

#! Ignored at the start of the first line (to make Unix-executable).

Types

"nil" "boolean" "number" "string" "table" "function" "thread" "userdata"

Note: for booleans, nil and false count as false, all the rest is true including 0 and "".

Variable scope

Variables are assumed to be global unless explicitly declared local . Local variables are lexically scoped: the scope of variables begins at the first statement after their declaration and lasts until the end of the innermost block that includes the declaration. For instance:

x = 10
do
local x = x
print(x)
x = x+1
do
local x = x+1
print(x)
end
print(x)
end
print(x)
-- global variable
-- new block
-- new `x', with value 10
--> 10
-- another block
-- another `x'
--> 12
--> 11
--> 10 (the global one)

Strings and escape sequences

' ' " " string delimiters; can be multi-line, escape sequences are ignored.

\a (bell) \b (backspace) \f (form feed) \n (newline) \r (return)

\t (horiz. tab) \v (vert. tab) \\ (backslash) \" (d. quote) \' (quote)

\[ (sq. bracket) \] (sq. bracket) \ddd (decimal)

Operators, decreasing precedence

^ (right-associative, math lib required)

not - (unary)

* /

+ -

.. (string concatenation, right-associative)

< > <= >= =      ~ (not equal)

and (stops on false/nil, returns last evaluated value)

or (stops on true (not false/nil), returns last evaluated value)

Assignment and coercion

a = 5 Simple assignment.

a = "hi" Variables are not typed, they can hold different types.

a, b, c = 1, 2, 3 Multiple assignment.

a, b = b, a Swap values, because right side values are evaluated before assignment.

a, b = 4, 5, 6 Too many values, 6 is discarded.

a, b = "there" Too few values, nil is assigned to b.

a = nil Destroys a, its value will be eligible for garbage collection if unreferenced.

a = z If z is not defined it is nil, so nil is assigned to a (destroying it).

a = "3" + "2" Numbers expected, strings are converted to numbers (a = 5).

a = 3 .. 2 Strings expected, numbers are converted to strings (a = "32").

Control structures

do block end Block with local scope.

while exp do block end Loop as long as exp is true.

repeat block until exp Exits when exp becomes true.

break Exits loop, must be last in block.

if exp then block { elseif exp then block} [ else block] end Conditional execution.

for var = start, end [, step] do block end Counter-based loop.

for vars in iterator do block end Iterator-based loop.

Table constructors

t = {} A new empty table.

t = {"yes", "no", "?"} Simple array, elements are t[1], t[2], t[3].

t = {[1] = "yes", [2] = "no", [3] = "?"} Same as line above.

t = {[-900] = 3, [+900] = 4} Sparse array, two elements (no space lost).

t = {x=5, y=10} Hash table, fields are t["x"], t["y"] or t.x, t.y.

t = {x=5, y=10; "yes", "no"} Mixed, fields / elements are t.x, t.y, t[1], t[2].

t = {msg = "choice", {"yes", "no", "?"}} Table containing a table as field.

Function definition

function name ( args ) body [return values] end Global function.

local function name ( args ) body [return values] end Function local to chunk.

f = function ( args ) body [return values] end Anonymous function.

function ( [args, ] ... ) body [return values] end Variable args, passed as arg[], arg.n.

function t.name ( args ) body [return values] end Shortcut for t.name = function [...]

function obj:name ( args ) body [return values] end Object function getting extra arg self.

Function call

f (x) Simple call, possibly returning one or more values.

f "hello" Shortcut for f ("hello").

f 'goodbye' Shortcut for f ('goodbye').

f see you soon Shortcut for f (see you soon).

f {x = 3, y = 4} Shortcut for f ({x = 3, y = 4}).

t.f (x) Calling a function stored in the field f of table t.

x:move (2, -3) Object call, shortcut for x.move (x, 2, -3), x will be assigned to self.

Namespace and File system

In NPL, files can be logically organized in namespaces. Each NPL runtimes maintains a dynamic mapping from namespaces to physical address (IP address); hence by modifying this mapping, files can have different physical configurations on the actual hardware. Please see Figure 1.

ALERT! Note: Could not convert this drawing frown

  • Namespace mapping
There are many ways to specify files in NPL. The most common way is to append the namespace before the file path, such as "NPC: creatures/dog.lua", where "NPC" is a namespace, "creatures/dog.lua" is a file path name. At runtime, "NPC" will be dynamically mapped to a computer (IP address), where the file "creatures/dog.lua" is located. When programmers are writing the code, they are not concerned with the target network topology; instead they group logics into namespaces.

NPL Foundation Interface

All NPL foundation interface is in the NPL table. They are useful for communications between NPL source files. Please refer to NPL reference [x] for more information. Please read this section side by side with section 2.4 .

NPL.activate (const char * sNeuronFile, const char * code = NULL )
Activate the specified file. It can either be glia file or neuron file, and it can either be local or using the default or specified DNS. All these information is extracted from the sNeuronFile parameters.
Parameters:
sNeuronFile : Neuron File name for this neuron file. It may contain activation type, namespace or DNS server information. They should be specified in the following format:
*[(g
gl l)] [ namespace : ] relative_path [ @ DNSServerName ]*
The following is a list of all valid file name combinations:
" NPC:NPL/CreatureA.lua@world " --neuron file with "NPL" namespace and a specified DNS
" NPC:NPL/CreatureA.lua " --neuron file with "NPL" namespace and local DNS
" NPL/CreatureA.lua " -- neuron file with default "NPL"(the first directory name) namespace and local DNS
" (g)NPL/CreatureA.lua " -- glia file, which will be sent to UI receivers and activated from there. So one can think of the content of glia file as a bunch of UI functions.
" (gl)NPL/CreatureA.lua " -- glia file forcing local activation: which will omit UI receivers and activate from local environment.
" (l)NPL/CreatureA.lua " -- local neuron file: which uses no namespace and no DNS, it is just treated as a local neuron file.
the "g
l" in the parenthesis denotes activation type. "g" is short for glia file. and "l" is short for local. if nothing is specified, it means neuron file. Both the neuron file and glia file can be local. A local file is always selected in the local NPL environment and execute from there. Currently, one can not specify namespace or DNS for glia file, they are always selected from the local NPL runtime environment.
If there is no namespace specified, the first directory path is used as the namespace. e.g "NPC:NPL/CreatureA.lua" is equivalent to "NPL/CreatureA.lua" NOTE: if relative_path contains "\\", it will be converted to "/".
code : It is a chunk of code that should be executed in the destination file. This code usually set the values of NPL global variables.

NPL. AddDNSRecord (const char * sDNSName, const char * sAddress)
Add a DNS server record to the current NPL runtime. DNS server record is a mapping from name to (IP:port) if one maps several IP:port to the same name, the former ones will be overridden.
Parameters:
sDNSName : the DNS server name. The DNS name "_world" is used for the current world DNS server. It is commonly used as a DNS reference to the current world that the user is exploring.
sAddress : "IP:port". e.g. "192.168.1.10:4000"

NPL. load (const char * filePath, bool bReload)
Load a new glia file (in the local environment) without running it. If the glia file is already loaded, it will not be loaded again. IMPORTANT: unlike other activation functions, this is more like "include()", the function will be loaded where it is and returned to the original caller upon finishing.
Parameters:
filePath : the local file path
bReload : if true, the file will be reloaded even if it is already loaded. Otherwise, the file will only be loaded if it is not loaded yet.
Returns:
Return the GliaFile reference.

NPL. this (const functor funcActivate)
Associate a user defined function as the activation function of this file. Add the current file name to the activate table. Create the activate table, if it does not exist.
Parameters:
__funcActivate
: the function pointer to the activation function. It can either be local or global.

NPL Source Files

There are two file types in NPL project, Neuron file, Glia file. Their difference in terms of code structure is trivial; however, their activation logic is non-trivial. For a complete discussion of neuron file versus Glia file, please see section 2.3 and papers in the reference section. A typical NPL source file (which applies to both file types) looks like this:

File name: SampleScript.lua
local function activate ()
if (state == nil) then --state is a global variable
local playname = "abc";
-- activate another script on remote machine.
NPL.activate ("ABC: /pol_intro.lua", "state=nil;");
-- activate another script on local machine.
NPL.activate ("(gl)polintro.lua","");
else
… …
end
end
state={}; -- a global variable (table), usually for passing and share states among NPL files.
NPL.this (activate); --tell NPL which function is used as the activation function of this file.

A script (or activation) file should define an activation function and explicitly tell NPL runtime by calling NPL.this (functionName) (See NPL.this in section 2.3 ).

Execution Sequence

When a NPL file is loaded, the entire file is compiled and executed. In other words, all of its functions are compiled but not executed and global variables initialized. Hence, NPL.this(…) is also executed at the time of loading. Normally a file will only be loaded once unless one explicitly reloads it. After a file is successfully loaded, it is put in to a passive state. A loaded file is only re-activated when a timer, an HCI event or some other file calls it by NPL.activate (…). Once a file is activated, its activation function is called and its global environment (variables) may contain useful information passed by the caller. Please note that NPL.activate can be synchronous or asynchronous, depending on the activation method used. If file activation takes place over a network, it is definitely asynchronous.

ParaEngine Extension Interface

ParaEngine extension interface is a group of API to write 2D and 3D content. It is only available with the distribution that comes with ParaEngine, a powerful distributed 3D computer game engine. The ParaEngine interface is organized in several tables listed below. For complete description, please see the NPL reference [x].

Table Name Description
ParaCamera The camera controller.
ParaScene ParaScene namespace contains a list of HAPI functions to create and modify scene objects in ParaEngine
ParaAsset ParaAsset namespace contains a list of HAPI functions to manage resources(asset) used in game world composing, such as 3d models, textures, animations, sound, etc.
ParaUI ParaUI namespace contains a list of HAPI functions to create user interface controls, such as windows, buttons, as well as event triggers.
ParaMisc Contains miscellaneous functions.
NPL Neural Parallel Language foundation interface is in this namespace.

Object Name Description
ParaObject It is represent a scene object.
ParaCharacter It is represent a character object.
ParaAssetObject It represents an assert entity
ParaUIObject It represents a GUI object

NPL Programming Guide

Introduction

The true power of NPL lies in its runtime support for (1) frequent code updates, (2) complex graphic user interface and human-computer interactions, and (3) reconfigurable code deployment in a distributed network environment. To utilize these features, programmers must understand some basic rules of the NPL runtime as well as the recommended programming paradigm.

It is worth mentioning that although NPL started as a standalone extensible language, its recent development is intertwined with ParaEngine, a distributed 3D computer game engine. Hence it is lucky that NPL now has a full-featured 2D and 3D programming API. This programming guide uses ParaEngine's API in NPL and examples also inherit this application background.

We will start from constructing simple 3D virtual world, and then we will cover event and trigger systems, finally we will come to the most difficult part, namely building dynamically evolving 3D virtual worlds on the network. It is in this last part that we demonstrate the real benefit of NPL and explains the network-transparent programming paradigm.

Readers are encouraged to take a look at our source code [x] that comes with the game. We will no longer cover NPL language syntax or the foundation interface in this chapter, please refer to Chapter 2 for such information.

Constructing Simple 3D Virtual World

Like writing 2D windows form applications, in creating 3D virtual world, we need to give 3D coordinates (with optional transformations, sizes, etc) for each scene object in the 3D virtual world. The major difference is that traditional 2D programming works on a finite screen, whereas 3D virtual world exists in a far larger space.

In ParaEngine API, the entire game world is partitioned into a great number of equal sized squares. Each square is linked with a loading script (script means NPL source file; they are used interchangeably in the following text). Some squares may be empty. When square region needs to be active, its associated script will be automatically activated, hence loading a collection of scene objects in to the game world. See Figure 2 .

ALERT! Note: Could not convert this drawing frown

  • Grid-based virtual world partition
For static 3D world, we have developed visual tools (as plug-in in 3dsmax) to generate world constructing scripts automatically. So it is actually a painless process. For more information, please read ParaEngine Document 1.7 [x]. The following figure shows how a 3D scene is constructed by scripts.

… (lots of code omitted)
local asset = LoadStaticMesh("", "sample/trees/tree1.x")
local player = CreateMeshPhysicsObject("",asset, 1,1,1, true, "0.193295,0,0,0,0.187032,-0.0488078,0,0.0488078,0.187032,0,0,0");
player:SetPosition(148,120.156,95);player:SetFacing(0);sceneLoader:AddChild(player);
… (lots of code omitted)
| |

  • From script to 3D scene
For 2D GUI, we also have a what-you-see-is-what-you-get (WYSIWYG) Integrated Development Environment (IDE), which is called Visual IDE for GUI. This IDE itself is written by NPL. Hence it is script that generates other scripts.

Events and Triggers in 2D/3D virtual worlds

In ParaEngine API, events and triggers are enabled for both 2D GUI objects and 3D scene objects. In NPL, an event must be specified as an NPL file, so that when the event is triggered, the corresponding file will be activated. In later sections, we will see that these event handling files may be on the same machine as the local visualization platform, or on remote server computers. For this section, we assume that all files are local to the visualization platform.

In case of 2D GUI object, events are triggered when a button is pressed or some text is entered, which gives the programmer a chance for implementing user-defined functionalities. Please refer to ParaEngine Document 4.2 for detailed information on creating 2D Graphic User Interface (GUI).

In case of 3D scene object (mostly characters), events are also triggered when other active scene objects collide with its perceptive radius or a mouse is clicked on it. This will allow programmers to write logics for contacts in the virtual worlds.

More over, there is a third kind of triggers, which is the timer. A timer will regularly activate a certain script at some fixed time interval. Timer is useful for broadcasting world state changes, playing cut-scene movies, doing regular jobs, etc.

The following code snippet shows how objects with event handlers can be created. Such code can also be automatically generated using Visual IDE for GUI [x].

local function activate()
local window, button, text; --declare local variables
--create a new window called "mainwindow" at (50,20) with size 600*400
window=ParaUI.CreateUIObject("container","mainwindow","_lt",50,20,600,400);
--attach the UI object to screen (root)
window:AttachToRoot();
--create a new button called "btnok" at (50,350) with size 70*30
button=ParaUI.CreateUIObject("button","btnok","_lt",50,350,70,30);
--attach the button to the window
window:AddChild(button);
--set text of the button
button.text="OK";
--if the button is clicked, delete the window
button. onclick ="(gl)script/empty.lua; ParaUI.Destroy(\"mainwindow\");";
--create a new text box called "txt" at (50,50) with size 500*300
text=ParaUI.CreateUIObject("text","txt","_lt",50,50,500,300);
--attach the text to the window
window:AddChild(text);
text.text="Hello world!!!";
--call the new function
manipulate();
end
NPL.this(activate);

Building Distributed Virtual World

Building distributed virtual world is not a trivial task. Distributed online game is the apex of computer technologies being applied to the Internet. It allows users to utilize all available resources on the network to build their own evolving virtual game world which are accessible to all visitors, and to share both game content (such as terrain, model, texture, animation, etc) and game logics (such as AI modules, NPC databases, etc. ) on the network.

With the advent of IPV6 and the increasing bandwidth and computing resources, we will soon witness more flexible and versatile services on the Internet. Large number of client/server based web architecture will be intertwined to service each other. The design of NPL is to make writing such software easier. Distributed on-line game will be the biggest beneficiary.

Before we start, programmers must understand some basic rules of the NPL runtime as well as the recommended programming paradigm.

NPL Terminologies

We will define the following terminologies first:

NPL source file: It is also called NPL file or Script file . It is any file that contains compilable NPL code.

Namespace: A namespace is a logical name which is dynamically mapped to a physical IP address at runtime.

NPL runtime: the NPL runtime environment compiles and executes NPL files, manages network connections with other NPL runtimes on the network, handles both local and remote file activation, automatically interprets namespaces to their physical addresses, acts as a distributed namespace mapping server, and doing all the dirty works of low-level data transmission on the network, etc. NPL runtime runs as a background service.

Neuron file: It is an NPL source file with explicitly defined activation functions. Global variables defined in NPL source file are not guaranteed to be accessible by other NPL source file.

Glia file: It is an NPL source file with explicitly defined activation functions. Global variables defined in glia file are shared by all other glia files in the same NPL runtime.

Activation file: It means any files with activation functions. It is the general name for neuron and glia file.

Activation chain: During the execution of an NPL file, the file may activate other NPL files; these other files may further activate other files. We define a file activation chain as the process of continues file activation from the triggering file to the file that no longer activate other files. The activation of the triggering file may originate from a 2D or 3D event in the virtual world.

UI receiver: A UI receiver contains a valid address of a certain NPL runtime instance on the network.

UI command: Also called visualization command . UI commands are a collection of functions in the ParaEngine API. The function parameters must or can be pre-evaluated. For example, PlaySound("track1.wav") is a valid UI command, because the parameter "track1.wav" can be evaluated. However, PlaySound(sSoundFilePath) is not a valid UI command, because sSoundFilePath is a variable and can not be evaluated.

Game Engine: It is the visualization and simulation platform of the virtual world. It receives and executes UI commands from its associated local NPL runtime. During world simulation, it also generates world events to the NPL runtime, such as 2D GUI trigger events, 3D object contact events, and timer events, etc.

Game Engine Instance: It is a copy of the game engine on the network. There can be any number of game engine instances on the shared network, such as the Internet.

The NPL Programming Paradigm

In NPL, logics are encoded in files, called NPL files. Please see the figure below. These files may be deployed on an arbitrary computer network that the script writer may not know at development time. At runtime, one neuron file may activate other files, and thus forms many file activation chains. Files may also be stimulated by a predefined event in the game world. It is important to note that all file activations are one directional, which means that there is no return message of a file activation call. Yet instead, during the execution of an NPL file, the file may emit visualization commands. These commands are ensured by their NPL runtimes to be received automatically by the appropriate game engine instance on the network. This is a similar mechanism improvised by the functioning of the biological neuron networks, which works together to provide our internal visual perceptions as well as interaction with the outside world. The runtime logic of NPL is very different from that of HTML and sever-side scripting such as ASP or PHP, etc. The tight integration of NPL runtime and ParaEngine makes composing distributed 3D game world possible and very practical. And we believe that NPL will eventually become a standard on Internet.

  • Activation chain and visualization command emission

Distributed Visualization Framework

Two major problems in distributed scripting systems are (1) data sharing (2) data visualization. The first problem can be solved by extending the addressing space from local to global and passing any certification information along with data access requests (which is automatically done by NPL runtimes). However, the second problem is not trivial and does not have a universal solution as the first one.

The ideal (NPL's) solution of scripting runtime for distributed games would be a type of visualization architecture where (1) each atomic script file can emit visualization commands anytime and anywhere during its execution; (2) these visualization commands wisely know to which game engine instance they go to, unless the script has the necessity to know all the game engine instances for which it has served.

A comparison of distributed visualization techniques used by other applications and NPL are given in Table 1 .

  • Technology Comparison of Distributed Visualization Framework
Short Desc Methodology Web Examples Drawbacks Advantage
File Duplication Files from another runtimes are duplicated (downloaded) to the local runtime for visualization. HTML / DHTML / Java scripts both time consuming and unsafe to synchronize local copies Easy to deploy and update.
Fixed Client /Server A single server computes visualizations for many view clients. ASP / PHP (server side scripting) Inflexible since all logics are for one server. Not easily scalable. Safe
NPL Tracking the file activation chain, sending visualization commands automatically to the triggering view client. NPL & ParaEngine Increased number of network connections. Safe, scalable and easy to deploy.

Example NPL Scenarios

For example, in our game demo, a player might simultaneously interact with several objects in a game scene. Their events will activate the corresponding scripts on several remote servers, which in turn may further activate other scripts on other severs. All scripts along the activation chain may issue visualization commands to change the 3D scene on the original player's computer.

A more concrete example is that one player is collided with another player in a game scene. What happens to the first player's runtime environment? One possible computing scenario behind it is stated below. The first player's runtime sends a message to the terrain host server, whose scripts will generate GUI commands to update player locations in the caller's world. Meanwhile, another message is sent to the second player's server. Scripts on this second server might generate a GUI command to display some text on the first player's world. The aggregated world changes for the first player are that the positions of all visible players are updated and that a piece of text is displayed in a popup dialog box. From the first player's viewpoint, the computing occurs on remote servers; GUI commands are issued from remote scripts, and then interpreted and executed in its local game engine instance.

  • NPLReference
Topic revision: r1 - 2008-04-02 - LiXizhi
 
This site is powered by the TWiki collaboration platform Powered by PerlCopyright © 2008-2024 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback