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)