Save/Load as plugin

Started by
2 comments, last by RobTheBloke 16 years, 4 months ago
Hello. I'm writing a 2d map editor. Now I have been thinking that it would be nice if I could make the loading and saving of maps as a plugin or something so the same application can save the data available in the map in any way the user wants to have his map. I have come up with some ways of doing this but I really would like some feedback on them. The map editor creates a large xml file containing all the data the mapeditor have about the map and then let the user parse it using an external application and save the bits (s)he needs and saves a level file using that. Pros: The user has total freedom writing the level. Cons: Creates much extra work (eg creating another application to save levels). Makes the loading of maps more difficult, the user must save a copy of the xml file in order to load the map in the editor again. The user must be able to program the application for saving. I write a simple "script language" where the data is accessible as global variables then the map editor interprets the script on saving and loading, the script language will only contain functions for creating files (text, binary) and similar things. Pros: The user does not have to be able to program at all, if the script language is easy enough. Cons: Not so flexible. Seems overly complicated. A plugin solution, I have no experience at all in this field but the user would create a dll file containing a save and load function and can then use the dll in the map editor. I don't even know if this is possible, but I think so from other plugin based application I've seen. Pros: User has total freedom writing their code. Flexible. If the users want to share save/load functions this is safer then using the standalone application way(?). Cons: Programming again. Well I am leaning towards the plugin solution, but I don't know how complicated this solution is. I'm writing the map editor in c# and I have heard somewhere that you can load dlls from all the languages in visual studio. If this is so then the user can write the save/load function in any of those languages, witch is a good feature. But how would I go about loading the dlls, can you load a dll in runtime and use it's functions, or can you define a folder containing dlls and load all them on startup? Would appreciate some discussion on this subject :) Thanks.
Advertisement
first, make sure that you have the concept of fully loading and saving the map in your own format - I wouldn't make this a plugin, but an intrinsic part of the system. Relying on plugins for fileIO of everything is a bad idea. You want a format that is guarenteed to load and save all data that is authored in your editor so that the user doesn't start screaming when they saved it as extension X, but no one bothered to impliment the load X function properly!

So, the next consideration is that other formats may or maynot use all of your editor data, in which case they are not loading and saving to that format (since it may be lossy and you lose a few bits of info). Instead, you are translating that data, so the concept of a plugin file translator is a wise one!

I typically would do something like this (and to make life easier, put all of this stuff into a C# class library so that it is accesible by other dll's without having to expose your app core - lets call this "mapeditor.dll"):

public class MapData{  // stuff in here... };public interface FileTranslator{  // will fill data with the data loaded from the file, data.  // if show options is true, the file translator should show an options  // dialog. useful to allow batch importing...  bool import(string filename,MapData data,bool showOptions);  // will write data to the specified file  // if show options is true, the file translator should show an options  // dialog. useful to allow batch exporting...  bool export(string filename,MapData data,bool showOptions);  // returns the extension for this file translator  string extension();  // returns some info, i.e. "Quake2 Map Format". Useful for building  // gui stuff in your app.  string information();};


So then, in our app, you want something like:
public static class FileTranslatorManager{  // will find the correct FileTranslator based on the extension,  // and pass the call to that FileTranslator::import  public static bool import(string filename,MapData data,bool showOptions);  // will find the correct FileTranslator based on the extension,  // and pass the call to that FileTranslator::export  public static bool export(string filename,MapData data,bool showOptions);  // register the file translator  public static registerTranslator(FileTranslator t);  // the registered translators  static List<FileTranslator> mFileTranslators;}


This should allow you to generate an export menu or whatever...

so. For each file translator you want to add, do something along the following lines (as a .cs file on it's own, or compiled into it's own class library. Not part of your main app):

public class MyTranslator : FileTranslator{  public bool import(string filename,MapData data,bool showOptions)  {     // todo    return false;  }  public bool export(string filename,MapData data,bool showOptions)  {     // todo    return false;  }  public string extension()   {    return "ext";  }  public string information()  {    return "some plugin file translator";  }};public static class Plugin{  public static FileTranslator create()  {    return new MyTranslator;  }}


now then, the fun part. You can use that code as either a script or a plugin dll. You'll basically want something along the lines of this: (added to some class somewhere)

/*requires the followingusing Microsoft.CSharp;using Microsoft.VisualBasic;using System.CodeDom;using System.CodeDom.Compiler;using System.Reflection;*/	// runs the code as a VB script                public bool ExecuteVB(string source)        {            VBCodeProvider prov = new VBCodeProvider();            ICodeCompiler compiler = prov.CreateCompiler();            return ExecuteScript(compiler,source);        }	// runs the code as a C# script        public bool ExecuteCS(string source)        {            CSharpCodeProvider  prov = new CSharpCodeProvider();            ICodeCompiler compiler = prov.CreateCompiler();            return ExecuteScript(compiler,source);        }                // loads the specified plugin DLL. (which is compiled using exactly the same source code        // as above. Just add a reference to the mapeditor.dll when you compile the class library)        //         public bool LoadPlugin(string dllname)        {        	Assembly assembly = Assembly.LoadFrom(dllname);        	if(assembly==null)        	  return false;        	return InvokeAssembly(assembly);        }	private bool InvokeAssembly(Assembly a)	{	    Type PluginType = a.GetType("Plugin");	    if(PluginType == null)	      return false;			    // grab the create method from the Plugin class. 	    MethodInfo mi = PluginType.GetMethod("create");            	            // create an instance of the "Plugin" class	    object o = a.CreateInstance("Plugin");	        	    // now invoke the create function... 	    object translator = mi.Invoke(o,null);	        	    FileTranslator ft = translator as FileTranslator;	    if(ft)	    {	      FileTranslatorManager.registerTranslator(ft);	      return true;	    }	    return false;	}        private bool ExecuteScript(ICodeCompiler compiler,string code)        {            CompilerParameters cp = new CompilerParameters();            cp.GenerateExecutable = true;            cp.GenerateInMemory = true;            // add any default libs you want            cp.ReferencedAssemblies.Add("system.dll");            cp.ReferencedAssemblies.Add("system.data.dll");            cp.ReferencedAssemblies.Add("system.drawing.dll");            cp.ReferencedAssemblies.Add("system.windows.forms.dll");            // add access to the dll with the map data structure in it!            cp.ReferencedAssemblies.Add("mapeditor.dll");            CompilerResults cr;            cr = compiler.CompileAssemblyFromSource(cp, code);            // check for any errors in the script            if (cr.Errors.HasErrors)            {	            StringBuilder sbErr;	            sbErr = new StringBuilder();	            foreach(CompilerError err in cr.Errors)                 {		            sbErr.AppendFormat("{0} at line {1} column {2} ",                        err.ErrorText,                         err.Line,			            err.Column);		            sbErr.Append("\n");	            }	            MessageBox.Show(sbErr.ToString(),"Script - Error");	            return;            }            // get the assembly code generated            Assembly a = cr.CompiledAssembly;            try             {	        return InvokeAssembly(a);            }            catch(Exception ex)            {                // this is called if an error gets generated	            MessageBox.Show(ex.Message);            }            return false;        }        


That should just about do it. I've not tested the code listed above, so it probably won't compile with some messing about, but hopefully should do roughly what you want.
You should be able to use any .NET language to create the dll, and VB or C# to write a script to do exactly the same thing.

\edit

I messed about with that code till it compiled. I've got an example project here. A few bits and bobs had to change, but it does work... enjoy.

[Edited by - RobTheBloke on November 22, 2007 10:57:19 AM]
Great help. Rating++

I read your post and got the basic idea of what to do, then I read it once more and saw that you had edited you post. Thanks for taking the time to make a compilable app, it was great help in visualize what to do.
I really don't have any questions, but if some arises I will post them here.

Seeing that this plugincode isn't as complicated as I believed it to be, I'm thinking of making my tool windows as plugins to. I have a couple of forms as tool windows right now. Do you think the same rule applies in this case to, like in IO to build a tool in the native application, so I make the tool windows I want in the code and then make the editor plugin-able?

And again, thanks a lot! The help is greatly appreciated.
there are no real problem making the scripts or plugins generate windows or custom controls. You may want to expose your windows form in the CoreAPI, or better still provide some way to be able to add a control to the main form (otherwise the scripts and plugins can do all sorts of nasty stuff). Just make sure that you add assemblies to the relevent dll's when compiling the scripts).

\edit

Just realised you can do this quite a bit easier by manually examining the classes within the plugin assembly. If the given type implements the FileTranslator interface, then you can just instantiate the class and skip the whole Plugin.Main stuff. Should make it easier to include a number of different class types in a given plugin or script. I had to kill the generateExecutable flag in the compile options, then the scripts can be compiled without Main().

revised source

\edit2

A little question, that i'm not sure of... Generally i'd prefer to put plugin dlls into a plugins directory. With the code there, if you do that it will automatically put MyCore.dll into that dir. When you then load the plugin it sees FileTranslator in the app as a different FileTranslator to the one in the plugin. This then means that it fails to load. Any ideas?

\edit3

ignore me. Just make sure the MyCore.dll gets deleted from the plugins dir before executing. So now bothe the scripts and plugins dir gets parsed, and any plug-ins or scripts are loaded. Included some VB and C++ example plugins and scripts as well...

[Edited by - RobTheBloke on November 23, 2007 11:34:05 AM]

This topic is closed to new replies.

Advertisement