diff --git a/Programs/Simian/ExtensionLoader.cs b/Programs/Simian/ExtensionLoader.cs
new file mode 100644
index 00000000..3be3b7ff
--- /dev/null
+++ b/Programs/Simian/ExtensionLoader.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.CodeDom.Compiler;
+using System.IO;
+using OpenMetaverse;
+
+namespace Simian
+{
+ public static class ExtensionLoader
+ {
+ ///
+ /// Exception thrown when there is a problem with an extension
+ ///
+ public class ExtensionException : Exception
+ {
+ public ExtensionException(string message)
+ : base(message)
+ {
+ }
+
+ public ExtensionException(string message, Exception innerException)
+ : base(message, innerException)
+ {
+ }
+ }
+
+ /// Currently loaded extensions
+ public static List Extensions;
+ ///
+ public static CodeDomProvider CSCompiler;
+ ///
+ public static CompilerParameters CSCompilerParams;
+
+ static ExtensionLoader()
+ {
+ Extensions = new List();
+
+ CSCompiler = CodeDomProvider.CreateProvider("C#");
+
+ CSCompilerParams = new CompilerParameters();
+ CSCompilerParams.GenerateExecutable = false;
+ CSCompilerParams.GenerateInMemory = true;
+ if (System.Diagnostics.Debugger.IsAttached)
+ CSCompilerParams.IncludeDebugInformation = true;
+ else
+ CSCompilerParams.IncludeDebugInformation = false;
+ CSCompilerParams.ReferencedAssemblies.Add("OpenMetaverseTypes.dll");
+ CSCompilerParams.ReferencedAssemblies.Add("OpenMetaverse.dll");
+ CSCompilerParams.ReferencedAssemblies.Add("Simian.exe");
+ }
+
+ ///
+ ///
+ ///
+ ///
+ /// server that these extensions belong to
+ public static void LoadAllExtensions(string path, Simian owner)
+ {
+ // Load internal extensions
+ LoadAssemblyExtensions(Assembly.GetExecutingAssembly(), owner);
+
+ // Load extensions from external assemblies
+ List extensionNames = ListExtensionAssemblies(path);
+ foreach (string name in extensionNames)
+ LoadAssemblyExtensions(Assembly.LoadFile(name), owner);
+
+ // Load extensions from external code files
+ extensionNames = ListExtensionSourceFiles(path);
+ foreach (string name in extensionNames)
+ {
+ CompilerResults results = CSCompiler.CompileAssemblyFromFile(CSCompilerParams, name);
+ if (results.Errors.Count == 0)
+ {
+ LoadAssemblyExtensions(results.CompiledAssembly, owner);
+ }
+ else
+ {
+ Logger.Log("Error(s) compiling " + name, Helpers.LogLevel.Error);
+ foreach (CompilerError error in results.Errors)
+ Logger.Log(error.ToString(), Helpers.LogLevel.Error);
+ }
+ }
+ }
+
+ public static List ListExtensionAssemblies(string path)
+ {
+ List plugins = new List();
+ string[] files = Directory.GetFiles(path, "Simian.*.dll");
+
+ foreach (string f in files)
+ {
+ try
+ {
+ Assembly a = Assembly.LoadFrom(f);
+ System.Type[] types = a.GetTypes();
+ foreach (System.Type type in types)
+ {
+ if (type.GetInterface("ISimianExtension") != null)
+ {
+ plugins.Add(f);
+ break;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log(String.Format("Unrecognized extension {0}: {1}", f, e.Message),
+ Helpers.LogLevel.Warning, e);
+ }
+ }
+
+ return plugins;
+ }
+
+ public static List ListExtensionSourceFiles(string path)
+ {
+ List plugins = new List();
+ string[] files = Directory.GetFiles(path, "Simian.*.cs");
+
+ foreach (string f in files)
+ {
+ if (File.ReadAllText(f).Contains("ISimianExtension"))
+ plugins.Add(f);
+ }
+
+ return plugins;
+ }
+
+ public static void LoadAssemblyExtensions(Assembly assembly, Simian owner)
+ {
+ foreach (Type t in assembly.GetTypes())
+ {
+ try
+ {
+ if (t.GetInterface("ISimianExtension") != null)
+ {
+ ConstructorInfo info = t.GetConstructor(new Type[] { typeof(Simian) });
+ ISimianExtension extension = (ISimianExtension)info.Invoke(new object[] { owner });
+ Extensions.Add(extension);
+ }
+ }
+ catch (Exception e)
+ {
+ Logger.Log("LoadAssemblyExtensions(): " + e.Message, Helpers.LogLevel.Warning);
+ }
+ }
+ }
+ }
+}
diff --git a/Programs/Simian/PacketHandlers.cs b/Programs/Simian/Extensions/ConnectionManagement.cs
similarity index 77%
rename from Programs/Simian/PacketHandlers.cs
rename to Programs/Simian/Extensions/ConnectionManagement.cs
index 783245d5..b420fad4 100644
--- a/Programs/Simian/PacketHandlers.cs
+++ b/Programs/Simian/Extensions/ConnectionManagement.cs
@@ -4,8 +4,27 @@ using OpenMetaverse.Packets;
namespace Simian
{
- public partial class Simian
+ public class ConnectionManagement : ISimianExtension
{
+ Simian Server;
+
+ public ConnectionManagement(Simian server)
+ {
+ Server = server;
+ }
+
+ public void Start()
+ {
+ Server.UDPServer.RegisterPacketCallback(PacketType.UseCircuitCode, new UDPServer.PacketCallback(UseCircuitCodeHandler));
+ Server.UDPServer.RegisterPacketCallback(PacketType.StartPingCheck, new UDPServer.PacketCallback(StartPingCheckHandler));
+ Server.UDPServer.RegisterPacketCallback(PacketType.CompleteAgentMovement, new UDPServer.PacketCallback(CompleteAgentMovementHandler));
+ Server.UDPServer.RegisterPacketCallback(PacketType.AgentUpdate, new UDPServer.PacketCallback(AgentUpdateHandler));
+ }
+
+ public void Stop()
+ {
+ }
+
void UseCircuitCodeHandler(Packet packet, Agent agent)
{
RegionHandshakePacket handshake = new RegionHandshakePacket();
diff --git a/Programs/Simian/ISimianExtension.cs b/Programs/Simian/ISimianExtension.cs
new file mode 100644
index 00000000..ed5625cc
--- /dev/null
+++ b/Programs/Simian/ISimianExtension.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Simian
+{
+ ///
+ /// Abstract base for rendering plugins
+ ///
+ public interface ISimianExtension
+ {
+ ///
+ /// Called when the simulator is initializing
+ ///
+ void Start();
+
+ ///
+ /// Called when the simulator is shutting down
+ ///
+ void Stop();
+ }
+}
diff --git a/Programs/Simian/README.txt b/Programs/Simian/README.txt
new file mode 100644
index 00000000..ef291698
--- /dev/null
+++ b/Programs/Simian/README.txt
@@ -0,0 +1,27 @@
+Introduction
+------------
+
+Simian is a lightweight simulator built on the libOpenMetaverse framework. Its
+primary uses are rapid prototyping of new designs, a lightweight benchmarking
+suite, and unit testing of client applications.
+
+Extensions
+------------
+
+Extensions can be written in one of three ways.
+
+1) Add a class that inherits from ISimianExtension directly in the Simian
+ project. Typically this is done by adding a new .cs file in the
+ extensions folder.
+
+2) Create a new assembly containing one or more extensions. The assembly must
+ follow the naming convention of Simian.*.dll.
+
+3) Put a source code file alongside the running Simian.exe binary that will be
+ compiled at runtime. The code must follow the naming convention Simian.*.cs.
+ Look at Simian.ViewerEffectPrinter.cs.example for an example. Remove the
+ .example extension and drop the file alongside the Simian.exe binary to see
+ it in action.
+
+All extensions must inherit from ISimianExtension and have a constructor that
+takes a Simian object as the only parameter.
diff --git a/Programs/Simian/Simian.ViewerEffectPrinter.cs.example b/Programs/Simian/Simian.ViewerEffectPrinter.cs.example
new file mode 100644
index 00000000..5ec8acc9
--- /dev/null
+++ b/Programs/Simian/Simian.ViewerEffectPrinter.cs.example
@@ -0,0 +1,30 @@
+using System;
+using OpenMetaverse;
+using OpenMetaverse.Packets;
+
+namespace Simian
+{
+ public class ViewerEffectPrinter : ISimianExtension
+ {
+ Simian Server;
+
+ public ViewerEffectPrinter(Simian server)
+ {
+ Server = server;
+ }
+
+ public void Start()
+ {
+ Server.UDPServer.RegisterPacketCallback(PacketType.ViewerEffect, new UDPServer.PacketCallback(ViewerEffectHandler));
+ }
+
+ public void Stop()
+ {
+ }
+
+ void ViewerEffectHandler(Packet packet, Agent agent)
+ {
+ Logger.Log("Received a ViewerEffect!", Helpers.LogLevel.Info);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Programs/Simian/Simian.cs b/Programs/Simian/Simian.cs
index b5b39675..4ce96b3d 100644
--- a/Programs/Simian/Simian.cs
+++ b/Programs/Simian/Simian.cs
@@ -13,8 +13,9 @@ namespace Simian
{
public partial class Simian
{
- HttpServer httpServer;
- UDPServer udpServer;
+ public HttpServer HttpServer;
+ public UDPServer UDPServer;
+
Dictionary unassociatedAgents;
int currentCircuitCode;
int tcpPort;
@@ -34,12 +35,24 @@ namespace Simian
InitUDPServer(udpPort);
InitHttpServer(tcpPort, ssl);
+
+ // Load all of the extensions
+ ExtensionLoader.LoadAllExtensions(AppDomain.CurrentDomain.BaseDirectory, this);
+
+ foreach (ISimianExtension extension in ExtensionLoader.Extensions)
+ {
+ Logger.DebugLog("Loading extension " + extension.GetType().Name);
+ extension.Start();
+ }
}
public void Stop()
{
- udpServer.Stop();
- httpServer.Stop();
+ foreach (ISimianExtension extension in ExtensionLoader.Extensions)
+ extension.Stop();
+
+ UDPServer.Stop();
+ HttpServer.Stop();
}
public bool TryGetUnassociatedAgent(uint circuitCode, out Agent agent)
@@ -59,17 +72,12 @@ namespace Simian
void InitUDPServer(int port)
{
- udpServer = new UDPServer(port, this);
-
- udpServer.RegisterPacketCallback(PacketType.UseCircuitCode, new UDPServer.PacketCallback(UseCircuitCodeHandler));
- udpServer.RegisterPacketCallback(PacketType.StartPingCheck, new UDPServer.PacketCallback(StartPingCheckHandler));
- udpServer.RegisterPacketCallback(PacketType.CompleteAgentMovement, new UDPServer.PacketCallback(CompleteAgentMovementHandler));
- udpServer.RegisterPacketCallback(PacketType.AgentUpdate, new UDPServer.PacketCallback(AgentUpdateHandler));
+ UDPServer = new UDPServer(port, this);
}
void InitHttpServer(int port, bool ssl)
{
- httpServer = new HttpServer(tcpPort, ssl);
+ HttpServer = new HttpServer(tcpPort, ssl);
// Login webpage HEAD request, used to check if the login webpage is alive
HttpRequestSignature signature = new HttpRequestSignature();
@@ -78,7 +86,7 @@ namespace Simian
signature.Path = "/loginpage";
HttpServer.HttpRequestCallback callback = new HttpServer.HttpRequestCallback(LoginWebpageHeadHandler);
HttpServer.HttpRequestHandler handler = new HttpServer.HttpRequestHandler(signature, callback);
- httpServer.AddHandler(handler);
+ HttpServer.AddHandler(handler);
// Login webpage GET request, gets the login webpage data (purely aesthetic)
signature.Method = "get";
@@ -87,7 +95,7 @@ namespace Simian
callback = new HttpServer.HttpRequestCallback(LoginWebpageGetHandler);
handler.Signature = signature;
handler.Callback = callback;
- httpServer.AddHandler(handler);
+ HttpServer.AddHandler(handler);
// Client XML-RPC login
signature.Method = "post";
@@ -96,7 +104,7 @@ namespace Simian
callback = new HttpServer.HttpRequestCallback(LoginXmlRpcPostHandler);
handler.Signature = signature;
handler.Callback = callback;
- httpServer.AddHandler(handler);
+ HttpServer.AddHandler(handler);
// Client LLSD login
signature.Method = "post";
@@ -105,9 +113,9 @@ namespace Simian
callback = new HttpServer.HttpRequestCallback(LoginLLSDPostHandler);
handler.Signature = signature;
handler.Callback = callback;
- httpServer.AddHandler(handler);
+ HttpServer.AddHandler(handler);
- httpServer.Start();
+ HttpServer.Start();
}
void LoginWebpageHeadHandler(ref HttpListenerContext context)
@@ -267,7 +275,7 @@ namespace Simian
{
uint circuitCode = (uint)Interlocked.Increment(ref currentCircuitCode);
- Agent agent = new Agent(udpServer, agentID, sessionID, secureSessionID, circuitCode);
+ Agent agent = new Agent(UDPServer, agentID, sessionID, secureSessionID, circuitCode);
// Put this client in the list of clients that have not been associated with an IPEndPoint yet
lock (unassociatedAgents)