IRegionModule
From OpenSimulator
(→The Base Interfaces) |
|||
(73 intermediate revisions by 7 users not shown) | |||
Line 1: | Line 1: | ||
− | + | {{Quicklinks|IRegionModule}} | |
− | {{ | + | |
<br /> | <br /> | ||
− | == | + | = 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 | ||
− | + | See [[Related Software]] for links to existing region modules. | |
− | + | = Introduction = | |
− | There are two types of region module. | + | Region modules are classes that implement OpenSimulator's INonSharedRegionModule or ISharedRegionModule interfaces. .NET DLLs containing these classes can then be placed in OpenSimulator's binary directory (bin/). On startup, OpenSimulator scans this directory and loads any modules found within DLLs 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 | * 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. | * Shared region modules, where a single module is shared between all regions/scenes running on the same simulator. | ||
− | Region Modules | + | = Building Region Modules = |
− | * The | + | == From Scratch == |
− | * | + | 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 | ||
+ | |||
+ | <source lang="xml"> | ||
+ | <Project frameworkVersion="v4_6" 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"> | ||
+ | <Exclude name="obj" pattern="obj"/> | ||
+ | </Match> | ||
+ | <Match path="Resources" pattern="*.*" buildAction="EmbeddedResource"/> | ||
+ | </Files> | ||
+ | </Project> | ||
+ | </source> | ||
+ | |||
+ | There are a number of things to note about this file | ||
+ | * The frameworkVersion should match the opensim one. See its setting in main opensim prebuild.xml file | ||
+ | * The path attribute in <Project> needs to point to the location in the addon-modules directory where your source code is located. This directory is relative to the root OpenSimulator path. In the example above, the code is in addon-modules/BareBonesNonSharedModule/src/BareBonesNonSharedModule. | ||
+ | * The <OutputPath> tags specify where your compiled module DLL is copied after it's built. This is relative to the location of your source code as specified in the path attribute of <Project>. In most cases, you want this to be placed in the OpenSimulator bin/ directory with the rest of the DLLs. Do not place this in the bin/addon-modules directory - confusingly this is for addon-module configuration files (*.ini) only. | ||
+ | * The <ReferencePath> value specify where the module project can find the DLLs that it needs to reference (OpenSimulator.Framework.dll, log4net.dll, etc.). | ||
+ | * Each library that you reference in your modules needs a <Reference> tag. The example above lists libraries such as OpenSim.Framework.dll and OpenMetaverse. | ||
+ | * For more examples you can look in the main prebuild.xml file in the root of the OpenSimulator source tree. Please note that the example above is streamlined compared to many of the entries there - the main file needs to be simplified at some stage sine there is a lot of redundancy (e.g. path does not need to be specified in each <Reference> if this is already given by a <ReferencePath>). | ||
+ | |||
+ | 4. Navigate back to the OpenSimulator root directory and run ./runprebuild.sh (Linux, Mac OSX) or ./runprebuild.bat (Windows). In the resulting output, near the top you should see the line | ||
+ | |||
+ | searchDirectory: addon-modules/BareBonesNonSharedModule | ||
+ | |||
+ | or similar. Now, 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 and appear in the output for the "show modules" console command. | ||
+ | |||
+ | == From existing module code == | ||
+ | If you are looking to build a region module that already exists, the steps are a little simpler. | ||
+ | |||
+ | 1. Place the module code inside the base OpenSimulator addon-modules directory. For example, if you are looking to compile a module called EventRecorderModule, then this will be situated at addon-modules/EventRecorderModule. Please be aware that the name of the module must match the patch in the prebuild.xml file (in this example, the file is EventRecorderModule/prebuild.xml). | ||
+ | |||
+ | 2. As above, re-run prebuild.sh/prebuild.bat. This is to insert the module into the OpenSimulator build solution (OpenSim.sln). | ||
+ | |||
+ | 3. You can now compile the module with Visual Studio, MonoDeveloper, xbuild or nant. | ||
+ | |||
+ | 4. On the next startup of OpenSimulator, the module should be loaded (the prebuild.xml places the built module within OpenSimulator's bin/). | ||
+ | |||
+ | =Installing Region Modules= | ||
+ | If you are just installing an existing region module DLL rather than building it from source then you will need to | ||
+ | |||
+ | 1. Copy the DLL and any other associated files into $OPENSIM_BASE/bin | ||
+ | |||
+ | 2. In most cases configure the module, either by adding the required section to OpenSim.ini, adding it as bin/addon-modules/MyModule/config/config.ini or by [[Configuring_Simulator_Parameters|explicitly including the module's configuration file]]. | ||
+ | |||
+ | =Region Module Interfaces= | ||
+ | All region modules must implement INonSharedRegionModule or ISharedRegionModule from OpenSim.Region.Framework.Interfaces. If a region module implements INonSharedRegionModule then an instance of that module is created for each region (aka scene) in the simulator. Each module only knows about its own region. If a region module implements ISharedRegionModule then only a single instance of the module exists and it is informed about all regions/scenes in the simulators. | ||
− | + | Both INonSharedRegionModule and ISharedRegionModule extend IRegionModuleBase which implements the bulk of the interface methods. These are as follows. | |
− | + | ||
<source lang="csharp"> | <source lang="csharp"> | ||
public interface IRegionModuleBase | 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(); | ||
} | } | ||
</source> | </source> | ||
Line 51: | Line 146: | ||
<tr> | <tr> | ||
<td>AddRegion</td> | <td>AddRegion</td> | ||
− | <td>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 | + | <td>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.</td> |
</tr> | </tr> | ||
<tr> | <tr> | ||
− | <td> | + | <td>RemoveRegion</td> |
− | <td> | + | <td>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.</td> |
</tr> | </tr> | ||
+ | <tr> | ||
+ | <td>RegionLoaded</td> | ||
+ | <td>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.</td> | ||
<tr> | <tr> | ||
<td>Close</td> | <td>Close</td> | ||
− | <td> | + | <td> |
+ | * For a non-shared region module, this will be called when its region is closed. This usually occurs on shutdown. However, it's also possible for a region to be removed whilst the simulator remains running. | ||
+ | * For a shared region module, this should be called when the simulator is shutdown. '''However, due to a regression in OpenSimulator 0.8 (and probably in many earlier versions) this was not occurring. This is fixed in current development code (from commit 889194d, 3rd July 2014). In the mean time, the easiest workaround is to count the number of scenes added to the module and then shutdown if the same number are removed. However, this will stop it being possible to operate a simulator with temporarily no regions (a somewhat minor use case). For an example of this approach, see https://github.com/justincc/EventRecordingModule/blob/master/src/EventRecorder/EventRecordingModule.cs''' | ||
</tr> | </tr> | ||
</table> | </table> | ||
− | + | INonSharedRegionModule itself contains no methods, being defined simply as | |
− | = | + | <source lang="csharp"> |
+ | public interface INonSharedRegionModule : IRegionModuleBase | ||
+ | { | ||
+ | } | ||
+ | </source> | ||
− | + | ISharedRegionModule has one additional method. | |
− | + | <source lang="csharp"> | |
+ | public interface ISharedRegionModule : IRegionModuleBase | ||
+ | { | ||
+ | void PostInitialise(); | ||
+ | } | ||
+ | </source> | ||
− | * scene.GetEntities() - returns a list of all the Entities in the environment. | + | <table cellpadding='5' border='1'> |
+ | <tr> | ||
+ | <th>Method</th> | ||
+ | <th>Description</th> | ||
+ | </tr> | ||
+ | <tr> | ||
+ | <td>PostInitialise</td> | ||
+ | <td>Guaranteed to be called after Initialise() on all other modules has been called but before modules are added.</td> | ||
+ | </tr> | ||
+ | </table> | ||
+ | |||
+ | = 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. | ||
+ | |||
+ | <source lang="csharp"> | ||
+ | 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); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </source> | ||
+ | |||
+ | == 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 OpenSimulator's module mechanism (which is currently [http://www.mono-project.com/Mono.Addins Mono.Addins]). | ||
+ | |||
+ | This is done by adding an Extension attribute to the class, for example. | ||
+ | |||
+ | <source lang="csharp"> | ||
+ | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "BareBonesNonSharedModule")] | ||
+ | </source> | ||
+ | |||
+ | 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: | ||
+ | |||
+ | <source lang="csharp"> | ||
+ | [assembly: Addin("BareBonesNonSharedModule", "0.1")] | ||
+ | [assembly: AddinDependency("OpenSim", "0.5")] | ||
+ | </source> | ||
+ | |||
+ | At the beginning of your module source code file you need to add this line: | ||
+ | |||
+ | <source lang="csharp"> | ||
+ | using Mono.Addins; | ||
+ | </source> | ||
+ | |||
+ | And prebuild.xml needs to include such a line for newer Mono versions: | ||
+ | |||
+ | <source lang="csharp"> | ||
+ | <Reference name="Mono.Addins.dll"/> | ||
+ | </source> | ||
+ | |||
+ | = Integrating with OpenSimulator = | ||
+ | |||
+ | NOTE: This section is very incomplete and parts are out of date. If you need to know something, please ask on the OpenSimulator [[mailing lists]] where we can both answer the question and add the necessary documentation here. | ||
+ | |||
+ | However, please note that OpenSimulator is a very young project and the internal interfaces can change at short notice. If this happens, it is up to you to keep your module up to date with later versions of OpenSimulator. | ||
+ | |||
+ | == Accessible Objects == | ||
+ | |||
+ | 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.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. | + | * scene.EventManager - this is the object from which you can register callbacks for scene events. Some examples provided below. |
* scene.RegionInfo - properties about the region | * scene.RegionInfo - properties about the region | ||
− | == Registering for | + | == Events == |
+ | |||
+ | |||
+ | <div style="background-color:#FFA0A0; padding:10px; padding-bottom:5px; border: 1px #FF544F solid"> | ||
+ | WARNING: most events are time critical. Your code must not delay them too much, like going a http connection... | ||
+ | </div> | ||
+ | |||
+ | Various occurrences in OpenSimulator (e.g. avatar entering a region, chat) have event hooks to which a module can subscribe. The major sets of events are available from Scene.EventManager. | ||
+ | |||
+ | In many cases, you want to do as little work as possible in the thread that fires the event as many coming from time-critical areas in the OpenSimulator code (e.g. within main scene frame processing) or areas where a holdup will cause other disruption (e.g. events which notify that a root agent has arrived). | ||
+ | |||
+ | These events are somewhat rough and ready because they were originally created for internal OpenSimulator use and by default modules started to use them. Thus, there is inconsistency with names, arguments and the events exposed. Please be prepared for all of these to change over time or be superseded by more appropriate events. | ||
+ | |||
+ | === Registering for events === | ||
Taking the [http://opensimulator.org/cgi-bin/viewcvs.cgi/trunk/OpenSim/Region/Environment/Modules/World/Sun/SunModule.cs?view=markup SunModule] as an example we can see the following code: | Taking the [http://opensimulator.org/cgi-bin/viewcvs.cgi/trunk/OpenSim/Region/Environment/Modules/World/Sun/SunModule.cs?view=markup SunModule] as an example we can see the following code: | ||
Line 86: | Line 329: | ||
... | ... | ||
</source> | </source> | ||
− | Pretty simple, we just got the EventManager and registered the SunUpdate method as a callback for the OnFrame event. | + | 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. So in this particular case, you want to be very careful about the actions you perform in this event as they will have a direct impact on scene loop performance (where taking a long time will result in symptoms of lag for moving avatars, etc.). |
− | + | Here's the function itself | |
<source lang="csharp"> | <source lang="csharp"> | ||
Line 113: | Line 356: | ||
</source> | </source> | ||
− | SunUpdate() takes no parameter (some events may require them). | + | 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. Moreover, you only want to do this for root agents (agents that actually have an attached avatar that can move around, etc.). Updates sent to child agents (which allow viewers to see into neighbouring regions) will simply be ignored. | ||
+ | |||
+ | === Available Events === | ||
+ | Here is an extremely incomplete list of available events (which hopefully should be filled out as time goes by). For more details, please see the OpenSimulator code itself. Please also note that OpenSimulator is still in development. Event parameters and names may change over time. | ||
+ | |||
+ | In the table below, the delay columns signal whether a delay in processing the event is likely to disrupt the simulator as a whole and/or the entity (e.g. a user) in question. However, please be aware that even if there is no immediate disruption from delaying one event thread, delaying many will eventually cause simulator wide problems as the threadpool is exhausted. | ||
+ | |||
+ | ==== In OpenSim.Region.Framework.Scenes.EventManager ==== | ||
+ | {| border = "1" | ||
+ | ! Event name !! Parameters !! Delay can disrupt simulator !! Delay can disrupt user !! Description | ||
+ | |- | ||
+ | | OnClientLogin || IClientAPI client || No || Yes || Triggered on the region entered by a client when it first logs in. A delay in processing this event will hold up the entrance of the avatar to the scene. | ||
+ | |- | ||
+ | | OnSetRootAgentScene || UUID agentID, Scene scene || No || Yes || Triggered when an avatar enters a scene and before any setup work has been done (such as initializing of attachments, etc.). A delay in processing this event will hold up the entrance of the avatar to the scene, which may be a particular issue with region crossings. | ||
+ | |- | ||
+ | | OnMakeRootAgent || ScenePresence presence || No || Yes || Triggered when an avatar enters a scene and after setup work has been done (recreation of attachment data, etc.). A delay in processing this event will hold up the entrance of the avatar to the scene, which may be a particular issue with region crossings. One can inspect the ScenePresence.TeleportFlags to determine if this is the initial login region for the viewer (ViaLogin) or whether their avatar is entering it in an existing session. | ||
+ | |- | ||
+ | | OnMakeChildAgent || ScenePresence presence || No || Yes || Triggered when an avatar is converted from a root agent to a child agent. This happens when an avatar moves away from a scene, either by teleporting or region cross. It is not fired when an avatar directly logs out. A delay in processing this event should not cause too much disruption to the avatar moving away, though a long delay may prevent them from successfully re-entering the scene. | ||
+ | |- | ||
+ | | OnClientClosed || UUID agentID, Scene scene || No || Yes || Triggered when a client is closed. This can be due to either a child agent no longer being needed or the user directly logging out from that region. If you want to distinguish between these two events, you will need to check the ScenePresence.IsChildAgent property. At this point, the agent will still be complete (e.g. it will have attachments registered to it) and it will be present in the scene graph. A delay in processing this event may cause issues if the user attempts to re-enter the simulator before the delay is complete. | ||
+ | |} | ||
− | + | = 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. | ||
+ | * [[New Region Modules|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. | ||
− | + | See also: [[Simian|Child Agent Establishment]] | |
− | + | ||
− | + | ||
− | + | ||
[[Category:Development]] | [[Category:Development]] | ||
+ | [[Category:Modules]] |
Latest revision as of 04:16, 2 April 2024
Languages: |
English Deutsch |
Contents |
[edit] 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
See Related Software for links to existing region modules.
[edit] Introduction
Region modules are classes that implement OpenSimulator's INonSharedRegionModule or ISharedRegionModule interfaces. .NET DLLs containing these classes can then be placed in OpenSimulator's binary directory (bin/). On startup, OpenSimulator scans this directory and loads any modules found within DLLs 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.
[edit] Building Region Modules
[edit] From Scratch
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_6" 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"> <Exclude name="obj" pattern="obj"/> </Match> <Match path="Resources" pattern="*.*" buildAction="EmbeddedResource"/> </Files> </Project>
There are a number of things to note about this file
- The frameworkVersion should match the opensim one. See its setting in main opensim prebuild.xml file
- The path attribute in <Project> needs to point to the location in the addon-modules directory where your source code is located. This directory is relative to the root OpenSimulator path. In the example above, the code is in addon-modules/BareBonesNonSharedModule/src/BareBonesNonSharedModule.
- The <OutputPath> tags specify where your compiled module DLL is copied after it's built. This is relative to the location of your source code as specified in the path attribute of <Project>. In most cases, you want this to be placed in the OpenSimulator bin/ directory with the rest of the DLLs. Do not place this in the bin/addon-modules directory - confusingly this is for addon-module configuration files (*.ini) only.
- The <ReferencePath> value specify where the module project can find the DLLs that it needs to reference (OpenSimulator.Framework.dll, log4net.dll, etc.).
- Each library that you reference in your modules needs a <Reference> tag. The example above lists libraries such as OpenSim.Framework.dll and OpenMetaverse.
- For more examples you can look in the main prebuild.xml file in the root of the OpenSimulator source tree. Please note that the example above is streamlined compared to many of the entries there - the main file needs to be simplified at some stage sine there is a lot of redundancy (e.g. path does not need to be specified in each <Reference> if this is already given by a <ReferencePath>).
4. Navigate back to the OpenSimulator root directory and run ./runprebuild.sh (Linux, Mac OSX) or ./runprebuild.bat (Windows). In the resulting output, near the top you should see the line
searchDirectory: addon-modules/BareBonesNonSharedModule
or similar. Now, 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 and appear in the output for the "show modules" console command.
[edit] From existing module code
If you are looking to build a region module that already exists, the steps are a little simpler.
1. Place the module code inside the base OpenSimulator addon-modules directory. For example, if you are looking to compile a module called EventRecorderModule, then this will be situated at addon-modules/EventRecorderModule. Please be aware that the name of the module must match the patch in the prebuild.xml file (in this example, the file is EventRecorderModule/prebuild.xml).
2. As above, re-run prebuild.sh/prebuild.bat. This is to insert the module into the OpenSimulator build solution (OpenSim.sln).
3. You can now compile the module with Visual Studio, MonoDeveloper, xbuild or nant.
4. On the next startup of OpenSimulator, the module should be loaded (the prebuild.xml places the built module within OpenSimulator's bin/).
[edit] Installing Region Modules
If you are just installing an existing region module DLL rather than building it from source then you will need to
1. Copy the DLL and any other associated files into $OPENSIM_BASE/bin
2. In most cases configure the module, either by adding the required section to OpenSim.ini, adding it as bin/addon-modules/MyModule/config/config.ini or by explicitly including the module's configuration file.
[edit] Region Module Interfaces
All region modules must implement INonSharedRegionModule or ISharedRegionModule from OpenSim.Region.Framework.Interfaces. If a region module implements INonSharedRegionModule then an instance of that module is created for each region (aka scene) in the simulator. Each module only knows about its own region. If a region module implements ISharedRegionModule then only a single instance of the module exists and it is informed about all regions/scenes in the simulators.
Both INonSharedRegionModule and ISharedRegionModule extend IRegionModuleBase which implements the bulk of the interface methods. These are 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 |
|
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 | Guaranteed to be called after Initialise() on all other modules has been called but before modules are added. |
[edit] 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); } } }
[edit] 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 OpenSimulator'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"/>
[edit] Integrating with OpenSimulator
NOTE: This section is very incomplete and parts are out of date. If you need to know something, please ask on the OpenSimulator mailing lists where we can both answer the question and add the necessary documentation here.
However, please note that OpenSimulator is a very young project and the internal interfaces can change at short notice. If this happens, it is up to you to keep your module up to date with later versions of OpenSimulator.
[edit] Accessible Objects
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 below.
- scene.RegionInfo - properties about the region
[edit] Events
WARNING: most events are time critical. Your code must not delay them too much, like going a http connection...
Various occurrences in OpenSimulator (e.g. avatar entering a region, chat) have event hooks to which a module can subscribe. The major sets of events are available from Scene.EventManager.
In many cases, you want to do as little work as possible in the thread that fires the event as many coming from time-critical areas in the OpenSimulator code (e.g. within main scene frame processing) or areas where a holdup will cause other disruption (e.g. events which notify that a root agent has arrived).
These events are somewhat rough and ready because they were originally created for internal OpenSimulator use and by default modules started to use them. Thus, there is inconsistency with names, arguments and the events exposed. Please be prepared for all of these to change over time or be superseded by more appropriate events.
[edit] 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. So in this particular case, you want to be very careful about the actions you perform in this event as they will have a direct impact on scene loop performance (where taking a long time will result in symptoms of lag for moving avatars, etc.).
Here's the function itself
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. Moreover, you only want to do this for root agents (agents that actually have an attached avatar that can move around, etc.). Updates sent to child agents (which allow viewers to see into neighbouring regions) will simply be ignored.
[edit] Available Events
Here is an extremely incomplete list of available events (which hopefully should be filled out as time goes by). For more details, please see the OpenSimulator code itself. Please also note that OpenSimulator is still in development. Event parameters and names may change over time.
In the table below, the delay columns signal whether a delay in processing the event is likely to disrupt the simulator as a whole and/or the entity (e.g. a user) in question. However, please be aware that even if there is no immediate disruption from delaying one event thread, delaying many will eventually cause simulator wide problems as the threadpool is exhausted.
[edit] In OpenSim.Region.Framework.Scenes.EventManager
Event name | Parameters | Delay can disrupt simulator | Delay can disrupt user | Description |
---|---|---|---|---|
OnClientLogin | IClientAPI client | No | Yes | Triggered on the region entered by a client when it first logs in. A delay in processing this event will hold up the entrance of the avatar to the scene. |
OnSetRootAgentScene | UUID agentID, Scene scene | No | Yes | Triggered when an avatar enters a scene and before any setup work has been done (such as initializing of attachments, etc.). A delay in processing this event will hold up the entrance of the avatar to the scene, which may be a particular issue with region crossings. |
OnMakeRootAgent | ScenePresence presence | No | Yes | Triggered when an avatar enters a scene and after setup work has been done (recreation of attachment data, etc.). A delay in processing this event will hold up the entrance of the avatar to the scene, which may be a particular issue with region crossings. One can inspect the ScenePresence.TeleportFlags to determine if this is the initial login region for the viewer (ViaLogin) or whether their avatar is entering it in an existing session. |
OnMakeChildAgent | ScenePresence presence | No | Yes | Triggered when an avatar is converted from a root agent to a child agent. This happens when an avatar moves away from a scene, either by teleporting or region cross. It is not fired when an avatar directly logs out. A delay in processing this event should not cause too much disruption to the avatar moving away, though a long delay may prevent them from successfully re-entering the scene. |
OnClientClosed | UUID agentID, Scene scene | No | Yes | Triggered when a client is closed. This can be due to either a child agent no longer being needed or the user directly logging out from that region. If you want to distinguish between these two events, you will need to check the ScenePresence.IsChildAgent property. At this point, the agent will still be complete (e.g. it will have attachments registered to it) and it will be present in the scene graph. A delay in processing this event may cause issues if the user attempts to re-enter the simulator before the delay is complete. |
[edit] 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.
See also: Child Agent Establishment