IRegionModule

From OpenSimulator

(Difference between revisions)
Jump to: navigation, search
(Example Region Module)
Line 80: Line 80:
 
= Example Region Module =
 
= Example Region Module =
  
Let's start off with the most basic region module possible. Below is a region module that does nothing more than log the events that it receives. Technically, it could be made a little simple by printing messages directly to the console rather than OpenSim's logging infrastructure but it's better to have it nicely integrated.
+
This example assumes that you are using the file already in the OpenSimulator source tree (from 0.7.1 onwards) at OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs. There's also a bare bones shared module example there. To build this as a separate project, please see the instructions above.
  
You can also find this example in OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs in OpenSim's source distribution from 0.7.1 onwards. There's also a bare bones shared module example there.
+
BareBonesNonShareadModule is very basic. It does nothing more than log calls made to IRegionModule methods in the process of activating the module.
  
 
In the source tree, the [Extension... attribute line is commented. You can uncomment this, rebuild OpenSimulator and start it to see the module in action.
 
In the source tree, the [Extension... attribute line is commented. You can uncomment this, rebuild OpenSimulator and start it to see the module in action.

Revision as of 11:18, 16 June 2014


Contents

Please Note

This page addresses the new region module mechanism, which has been in place since at least OpenSimulator 0.6.9. For an older version of this page that references the older IRegionModule mechanisms, please see http://opensimulator.org/index.php?title=IRegionModule&oldid=13166

Introduction

Region modules are .net/mono DLLs. During initialization of the simulator, the OpenSimulator binary directory (bin/) is scanned for DLLs, to load the region modules that are stored there.

Region modules execute within the heart of the simulator and have access to all of its facilities. This makes them very powerful but also means that extra care needs to be taken when creating them in order to make sure that they do not have an adverse effect on simulator performance.

Typically, region modules register methods with the simulator's event manager to be called when various events occur (e.g. avatar chat, user entering a region, etc.).

There are two types of region module.

  • Non-shared modules where a separate module is created for each region/scene
  • Shared region modules, where a single module is shared between all regions/scenes running on the same simulator.

Region Modules require a few basic things:

  • The Base Interface
  • Some callbacks for OpenSimulator events

Building Region Modules

At this time, region modules are typically built within the OpenSimulator source tree itself, using its build mechanisms. The steps are as follows

1. Navigate to the addon-modules/ directory in the base of the OpenSimulator source tree (not the bin/addon-modules/ directory in the bin tree that we will talk about in a bit).

2. Create a directory for the region module project, typically with the same name as the region module. For example BareBonesNonSharedModule/

3. Create a prebuild.xml for the project. This is the file used by ./runprebuild.sh or ./runprebuild.bat in the base OpenSimulator directory to create the appropriate build files for Visual Studio, Monodevelop and nant. For a very basic module, you would have something like

<Project frameworkVersion="v4_0" name="BareBonesNonSharedModule" path="addon-modules/BareBonesNonSharedModule/src/BareBonesNonSharedModule" type="Library">
  <Configuration name="Debug">
    <Options>
      <OutputPath>../../../../bin</OutputPath>
    </Options>
  </Configuration>
  <Configuration name="Release">
    <Options>
      <OutputPath>../../../../bin</OutputPath>
    </Options>
  </Configuration>
 
  <ReferencePath>../../../../bin/</ReferencePath>
  <Reference name="System"/>
  <Reference name="log4net"/>
  <Reference name="Mono.Addins"/>
  <Reference name="Nini"/>
  <Reference name="OpenMetaverse"/>
  <Reference name="OpenMetaverseTypes"/>
  <Reference name="OpenSim.Framework"/>
  <Reference name="OpenSim.Region.Framework"/>
  <Reference name="OpenSim.Services.Interfaces"/>
 
  <Files>
    <Match pattern="*.cs" recurse="true"/>
    <Match path="Resources" pattern="*.*" buildAction="EmbeddedResource"/>
  </Files>
</Project>

4. Navigate back to the OpenSimulator root directory and run ./runprebuild.sh (Linux, Mac OSX) or ./runprebuild.bat (Windows). If you are using an IDE such as Visual Studio or MonoDevelop, you should now be able to reload the OpenSim.sln solution and see your project within the source tree.

If anything goes wrong, look carefully at the output for runprebuild.sh/.bat. You may see messages such as

[!] Could not resolve Solution path: addon-modules/BareBonesNonSharedModule/src/BareBonesNonSharedModule

which indicate that your prebuild.xml file for that module is not correctly picking up the source folder.

5. Add or edit module files in your project as required.

6. Re-run runprebuild.sh if you have added any new files to your module.

7. Build OpenSim.sln using your usual build method, whether that's within the IDE or on the command line with xbuild (mono) or nant.

8. If compilation succeeds, the build process will copy the binary into the OpenSimulator bin/ directory. When you next restart OpenSimulator, it should be loaded into the simulator.

Example Region Module

This example assumes that you are using the file already in the OpenSimulator source tree (from 0.7.1 onwards) at OpenSim/Region/OptionalModules/Example/BareBonesNonShared/BareBonesNonSharedModule.cs. There's also a bare bones shared module example there. To build this as a separate project, please see the instructions above.

BareBonesNonShareadModule is very basic. It does nothing more than log calls made to IRegionModule methods in the process of activating the module.

In the source tree, the [Extension... attribute line is commented. You can uncomment this, rebuild OpenSimulator and start it to see the module in action.

using System;
using System.Reflection;
using log4net;
using Mono.Addins;
using Nini.Config;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
 
[assembly: Addin("BareBonesNonSharedModule", "0.1")]
[assembly: AddinDependency("OpenSim", "0.5")]
 
namespace OpenSim.Region.OptionalModules.Example.BareBonesNonShared
{
    /// <summary>
    /// Simplest possible example of a non-shared region module.
    /// </summary>
    /// <remarks>
    /// This module is the simplest possible example of a non-shared region module (a module where each scene/region
    /// in the simulator has its own copy).
    ///
    /// When the module is enabled it will print messages when it receives certain events to the screen and the log
    /// file.
    /// </remarks>
    [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "BareBonesNonSharedModule")]
    public class BareBonesNonSharedModule : INonSharedRegionModule
    {
        private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
 
        public string Name { get { return "Bare Bones Non Shared Module"; } }        
 
        public Type ReplaceableInterface { get { return null; } }
 
        public void Initialise(IConfigSource source)
        {
            m_log.DebugFormat("[BARE BONES]: INITIALIZED MODULE");
        }
 
        public void Close()
        {
            m_log.DebugFormat("[BARE BONES]: CLOSED MODULE");
        }
 
        public void AddRegion(Scene scene)
        {
            m_log.DebugFormat("[BARE BONES]: REGION {0} ADDED", scene.RegionInfo.RegionName);
        }
 
        public void RemoveRegion(Scene scene)
        {
            m_log.DebugFormat("[BARE BONES]: REGION {0} REMOVED", scene.RegionInfo.RegionName);
        }        
 
        public void RegionLoaded(Scene scene)
        {
            m_log.DebugFormat("[BARE BONES]: REGION {0} LOADED", scene.RegionInfo.RegionName);
        }                
    }
}

The Base Interfaces

Now let's discussion the components of the example above. Region modules must implement INonSharedRegionModule or ISharedRegionModule as appropriate - the example above implements INonSharedRegionModule. However, both these interfaces extend IRegionModuleBase, defined as follows.

public interface IRegionModuleBase
{
     string Name { get; }
     Type ReplaceableInterface { get; }
     void Initialise(IConfigSource source);
     void AddRegion(Scene scene);
     void RemoveRegion(Scene scene);
     void RegionLoaded(Scene scene);
     void Close();
}
Method Description
Name This name is shown when the console command "show modules" is run. For example, "Sim Chat Module" or "The Best Region Module Ever".
ReplaceableInterface If this is not null, then the module is not loaded if any other module implements the given interface. One use for this is to provide 'stub' functionality implementations that are only active if no other module is present
Initialise This method is called immediately after the region module has been loaded into the runtime, before it has been added to a scene or scenes. IConfigSource is a Nini class that contains the concatentation of config parameters from OpenSim.ini, OpenSimDefaults.ini and the appropriate ini files in bin/config-include
AddRegion This method is called when a region is added to the module. For shared modules this will happen multiple times (one for each module). For non-shared modules this will happen only once. The module can store the scene reference and use it later to reach and invoke OpenSimulator internals and interfaces.
RemoveRegion Called when a region is removed from a module. For shared modules this can happen multiple times. For non-shared region modules this will happen only once and should shortly be followed by a Close(). On simulator shutdown, this method will be called before Close(). RemoveRegion() can also be called if a region/scene is manually removed while the simulator is running.
RegionLoaded Called when all modules have been added for a particular scene/region. Since all other modules are now loaded, this gives the module an opportunity to obtain interfaces or subscribe to events on other modules. Called once for a non-shared region module and multiple times for shared region modules.
Close This method will be invoked when the sim is closing down.

INonSharedRegionModule itself contains no methods, being defined simply as

public interface INonSharedRegionModule : IRegionModuleBase
{
}

ISharedRegionModule has one additional method.

public interface ISharedRegionModule : IRegionModuleBase
{
    void PostInitialise();
}
Method Description
PostInitialise Called after Initialise() but before modules are added. This may be a bug - it might be that this should really be called after all the regions have been added.

Enabling the module

Creating the module code itself isn't quite enough to enable it. To do that, we need to make it visible to OpenSim's module mechanism (which is currently Mono.Addins).

This is done by adding an Extension attribute to the class, for example.

[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "BareBonesNonSharedModule")]

The important part here is the "Path" section "/OpenSim/RegionModules" - this is how OpenSimulator retrieves modules from Mono.Addins. The Id can be anything meaningful to the module.

Newer Mono versions also need this kind of section before the namespace declaration:

[assembly: Addin("BareBonesNonSharedModule", "0.1")]
[assembly: AddinDependency("OpenSim", "0.5")]

At the beginning of your module source code file you need to add this line:

using Mono.Addins;

And prebuild.xml needs to include such a line for newer Mono versions:

<Reference name="Mono.Addins.dll"/>

Integrating with OpenSimulator

NOTE: This section is now very out of date - NEEDS WORK!

Accessible Objects

Note: these are internal interfaces, and will change in the future, probably for the better. We expect these to stabilize over time, but for now this point in time snapshot is probably helpful.

In the AddRegion routine you get access to the scene object for the region, from here you can spider down into the scene and get access to many other objects of interest.

  • scene.GetEntities() - returns a list of all the Entities in the environment. This will be a combined list of SceneObjectGroups (prim sets) and ScenePresences (avatars).
  • scene.GetAvatars() - get only the avatars in the scene (very handy for sending messages to clients)
  • scene.EventManager - this is the object from which you can register callbacks for scene events. Some examples provided in a little bit
  • scene.RegionInfo - properties about the region

Registering for Events

Taking the SunModule as an example we can see the following code:

In Initialise():

...
m_scene.EventManager.OnFrame += SunUpdate;
...

Pretty simple, we just got the EventManager and registered the SunUpdate method as a callback for the OnFrame event. OnFrame is triggered every time there is a render frame in opensim, which is about 20 times per second. If you are firing on the OnFrame event you need to do something small, or punt most of the time, as you'll negatively impact the performance of the system otherwise.

Now, for that function...

public void SunUpdate()
{
    // this code just means only do this on every 1000th frame, and don't do it if the sun is in a fixed possition
    if (((m_frame++%m_frame_mod) != 0) || !ready || sunFixed)
    {
        return;
    }
 
    GenSunPos();        // Generate shared values once
 
    List<ScenePresence> avatars = m_scene.GetAvatars();
    foreach (ScenePresence avatar in avatars)
    {
        if (!avatar.IsChildAgent)
            avatar.ControllingClient.SendSunPos(Position, Velocity, CurrentTime, SecondsPerSunCycle, SecondsPerYear, OrbitalPosition);
    }
 
    // set estate settings for region access to sun position
    m_scene.RegionInfo.RegionSettings.SunVector = Position;
}

SunUpdate() takes no parameter (some events may require them). It only fires every 1000th frame by default (m_frame_mod = 1000 in this module), so it doesn't take too many cycles.

In order for the sun position to change for the clients, they need to be told that it changes. This is done by getting a list of all the Avatars from the scene, then sending the Sun Position to each of them in turn. It is important to check to see if the avatar is a ChildAgent, otherwise you will generate zombies in opensim world.

Where to go from here

  • Getting Started with Region Modules -- the Hello World of OpenSimulator application development. Rather old by now but still worth a look.
  • http://bluewallvirtual.com/example_region_module - More shared region module example code by Bluewall.
  • Development discussion of the current region module mechanism
  • Read the source for existing opensim core modules. These are in the OpenSim.Region.CoreModules and OpenSim.Region.OptionalModules projects. Looking through this code is an extremely good way to find out what region modules can do and how they can do it.
  • Read the source code for the EventManager class in the OpenSim.Region.Framework.Scenes project. These list the many events that exist. Some modules may also export their own events (e.g. OnInventoryArchiveSaved in the InventoryArchiverModule at OpenSim/Region/CoreModules/Avatar/Inventory/Archiver/).
  • Help write some examples here. OpenSimulator grows with your contributions.
Personal tools
General
About This Wiki