Plugin System: Python Interpreter or C++ DLLs

Started by
8 comments, last by Flyverse 8 years, 10 months ago

Hey guys!

In my current project (A platformer), the modding API has to be really well done, since I want every object, entity, etc, to be a mod itself (I talked a bit about that in my other threads). Basically, here is my simplified architecture:

Let's assume I have a mod "PlatformX". The restrictions for the mod, speaking of code, are simple; If it is an entity, a drawable thing needs to be loaded, a hitbox needs to be created, and it needs a list of functions for its behaviour (More about that later). So it would look like this:


//Each function is allowed to access specific variables of my game code.
init(): //Function called when loading the mod
    this.texture = load it //Instantiate the texture of this entity
    this.hitbox = load it //Instantiate the hitbox/collision of this entity
    hookInto(EventType::Update, entityUpdate) //Hook into the update event. explained later.
    hookInto(EventType::Collide, randomfunctionname_for_entityCollide) //Same here

entityUpdate(float delta):
    doSomething

randomfunctionname_for_collidesWith(Entity A):
    doSomething

Now some (bad) examples: (Will edit later)


map<EventType, vector<FunctionPointer>> events

gameUpdate():
    for each FunctionPointer mapped to EventType::Update, execute the function
    otherStuff


physicsColliding(Entity A, entity B):
    if(A.hookedInto(EventType::Collide))
        somehow_get_A's_collision_method(B)


So, what is this event system all about? Basically, I want to be able (And already am) to fire an event for a specific ID (Such as EventType::Update), and pass it arguments. Every method hooked into this event will then be called with those arguments. This means, that every entity that hooked, for example, into the update event, will have its update function called every 30ms. My current problem is how I'll load and execute the plugins. I found two solutions, but both are unconvenient, so not real solutions.

First one: Use a python interpreter. I would have to write some interfaces for the python code, but having all the code running in the python interpreter would be a good way to allow plugins to only access specific variables, and each "class" could also define it's own local variables. Unfortunately, there are two major problems:

1. Performance. Python isn't C++, and letting such crucial things as update-code run through a python interpreter (+ The overhead generated by needing to call these methods) would probably be extremely slow.

2. I somehow need to link each entity to its own methods. Since I can't create a C++ class at runtime, I would have to find a way to (for example) call the randomfunctionname_for_collidesWith(Entity A) method only if the entity owning this method collided with something. I don't think this is a big problem though.

Second one: C++ DLLs. This actually was my first idea. But I read somewhere that the compiler version absolutly has to be the same as the one the main program was written in, and that there are many other constraints.

The big pro's would be that I could just define classes for my new entities, and store them in a list or something.

What do you guys think I should do? I'm sorry that I did not explain that well, but I somehow forgot what I wanted to say (Started writing this post a week ago and forgot to continue since then)

Kind regards

Advertisement

So who is writing these plug-ins? If you intend to let users download and run plug-ins written by other users, then both approaches present some major security headaches and C++ plug-ins represent a major stability concern. Also, is cross-platform a concern? Do you want to release your code on, for example, Windows and OS X and have a single plug-in work for both? If so, then C++ is pretty much right out. Other options with fewer security concerns are Lua and AngelScript. Both of them require you to explicitly expose whatever non-core language functionality you want, so it's a lot less likely that some could do things like sniff passwords, rummage through the users address book or act as a node of a bot net from the plug-in code.

Also, it's possible in Python (and many other scripting languages like AngelScript) to create classes that implement a C++ interface (for example, it's one of the things covered in the boost::python tutorial), so if you did go the Python route, it's possible to stick all the plug-in objects in a list as well. And it's equally possible to implement some plug-ins in C++ and some in a scripting language like Python or AngelScript (or both) so you don't actually have an either/or situation here.

@SirCrane

I don't think that there will be a major security issue, since at first most plugins will be written by myself, and plug-ins written by other users later one will have to get approved by myself (At least thats how I picture it right now)

Cross-Platform would be really cool, but I'd rather just build for Windows and at least have gotten something done rather than being stuck forever. Scripting languages are just too slow, since there will be a need for an interpreter to be running the whole time - And a huge part of the games core functionality will be executed through these plug-ins. Also the whole idea behind these plug-ins is messing with my mind, since I really have problems imagining how to link all that stuff together. (By the way, I now remember one thing that I forgot to write about in the original post. Going to edit it now in order to add some pseudo-code)

(Will continue editing later on)

Scripting languages are not necessarily fully interpreted at run time. A number of scripting languages feature just in time compilation which turns the script or pre-compiled byte code into machine code at run time. For example, some versions of AngelScript support JIT, as does the SpiderMonkey JavaScript implementation.

If you're limiting yourself to Python, then yes, speed is an issue. Python (specifically the standard CPython implementation) is one of the slowest scripting languages in popular use. It's embarrassingly bad.

Modern JavaScript implementations are very fast. LuaJIT is scary fast (possibly the fastest scripting language implementation that exists today). C# implementations like Mono are quite fast; fast enough for Unity! There are faster implementations of Python, though none I know of that you can just drop in easily. There are even C++ interpreters and JIT engines (some even have sandboxing techniques to avoid the serious security issues of C++ plugins).

That said, the speed is probably less of an issue than you think. Very successful AAA games have been written with copious amounts of Python in them, and that's with the standard, terrible CPython interpreter.

Not all code _has_ to be fast. Your core rendering, physics, navigation, etc. should all be built-in to your engine and written in C++. Leave the "modding" to gameplay code. As long as they aren't foolishly registering for too many events or trying to do complex work in the scripts, they'll be fine. That's kind of the whole point of an engine vs a game: the engine code does the real work while the game code tells the engine what work needs to be done.

If a user needs to modify the core behavior then they can just modify the engine code (assuming you give/sell them source code). There's not a strong reason to make the core engine super moddable. Over-abstracting the innards of your engine just makes it unnecessarily slow (C++ or not) and makes it a massive pain in the butt to work with. Simpler is better.

Sean Middleditch – Game Systems Engineer – Join my team!

Thanks for the answers. I guess I'll look into the LuaJIT stuff then.

And in the first place the whole modding system is only for myself anyway. Call it overengineering, but I absolutely want to do it, and to do it right :)

First one: Use a python interpreter. I would have to write some interfaces for the python code, but having all the code running in the python interpreter would be a good way to allow plugins to only access specific variables, and each "class" could also define it's own local variables. Unfortunately, there are two major problems:
1. Performance. Python isn't C++, and letting such crucial things as update-code run through a python interpreter (+ The overhead generated by needing to call these methods) would probably be extremely slow.
2. I somehow need to link each entity to its own methods. Since I can't create a C++ class at runtime, I would have to find a way to (for example) call the randomfunctionname_for_collidesWith(Entity A) method only if the entity owning this method collided with something. I don't think this is a big problem though.


Performance is not an issue. Your scripts should never be run every frame and should not be running any "critical path" stuff. Scripts are for handling "exceptional" cases. For example, doors should play an open animation when clicked on - but if it's scripted it might check to see if the player has a key before playing said animation.

Python does let you create C++ classes at runtime (how else would it create its own objects? ;) ). You can either tie it in to Python in such a way that your C++ object looks and behaves like a native Python object, or you could write some sort of "CreateWidget" Python function that returns a new instance.

Second one: C++ DLLs. This actually was my first idea. But I read somewhere that the compiler version absolutly has to be the same as the one the main program was written in, and that there are many other constraints.
The big pro's would be that I could just define classes for my new entities, and store them in a list or something.


This is not recommended if you expect end users to write them - even if you want to approve each mod yourself (and you don't, trust me). There are too many ways to harm a user's computer with C++ that you simply cannot check even with the most rigorous testing process.

You're also ignoring the fact that C++ simply cannot have DLLs. C++ does not define a binary interface, so the only DLLs that can use C++ must be compiled with exactly the same compiler and compiler version the game is made with. If you did want to do native DLLs, then you want a C API, not a C++ one.

Plus, the change/build/test cycle on C++ is pretty horrible compared to scripts which, if you do it right, can even be reloaded at runtime without shutting your game down.

In conclusion: Scripts are preferred. They have faster iteration time, can be sandboxed, do not have platform dependency issues, and their only real downside is they are slower than C++ code. And their speed can be mitigated with smart JITers (like other commenters have pointed out) or by smart design choices as to when and where scripts are used.

Heck, I wrote a scripting language where most native calls have to be synced to the framerate for threading reasons, and while we've had to make certain design choices based around this speed concern (i.e. ways to ask the game to tell us when something happens, native functions doing large chunks of work, etc), the benefits of the system have far outweighed the performance issues. And it's actually much more performant than the previous system where all scripts were run, top to bottom, every single frame.

Thanks for your answer.

The problem lies in the concept I want to achieve. I know scripts should not be used to do stuff every frame, but exactly that is what I want to do; Or at least I want it to be possible (Even if I don't know for what exactly I would use an update function in my modifications, it could be useful. Basically I want to keep myself all possibilities open.).

Also, I never did this, so I have no clue of how I can make specific C++ variables accessible to the script and vice versa: Also, the script should be able to modify some of these variables (For example on collision callback: The script should be able to modify variables marked as owned by the script.). But, hey, I guess I'll just have to learn how to do this somewhere.

Thanks for your answer.

The problem lies in the concept I want to achieve. I know scripts should not be used to do stuff every frame, but exactly that is what I want to do; Or at least I want it to be possible (Even if I don't know for what exactly I would use an update function in my modifications, it could be useful. Basically I want to keep myself all possibilities open.).


"Keep all possibilities open" is a great way to never finish anything smile.png Implement what you currently need. If you need more flexibility in the future, you can add it in once you know you need it.

Nothing is inherently wrong with doing something in script every frame, as long as you keep in mind that scripts generally consume more processing resources and memory then equivalent C++ code. So if you do want to do something every frame, you want to do as little as you can get away with, on as few objects as possible. (I.e. only run a Update() function in script, rather then running the whole thing end-to-end like a batch file)

Also, I never did this, so I have no clue of how I can make specific C++ variables accessible to the script and vice versa: Also, the script should be able to modify some of these variables (For example on collision callback: The script should be able to modify variables marked as owned by the script.). But, hey, I guess I'll just have to learn how to do this somewhere.


I would recommend against exposing variables directly. Some languages will let you do this, but you're giving up the ability to restrict the values that variable should be, and may run into threading issues.

It is better to add read/write functions that you can then edit on the C++ end. Some script languages will let you expose those two functions as a "property" which looks like a variable on the script side, but calls the two functions internally. That way you can add threading locks, range checks, or even change the type of storage in the C++ code without altering the script.

Thanks for the answer.

I read some articles and blog posts about Lua in C++ and I'm slowly beginning to really like it. Only problem now is, that I still can't figure out how I want to link my scripts together with my game code. On one side I got the event system, which isn't that hard to do; But I have no clue how I should implement game-entities. For me, a game entity should be an object with:

- A drawable

- A collision-shape

- A position

+ Local variables that will only be created and used in Lua.

+ Some functions, who all just are callbacks of the event system.

Thing is: How the hell can I keep this stuff together? When loading a Lua entity, I somehow need to convert the 4 variables into C++, while letting them be accessed by the Lua code. At first I wanted to only have one instance per entity type, but then I wouldnt be able to keep different positions etc... Meh, there's even more to it, but I think I will just have to open another thread if I still can't figure it out.

This topic is closed to new replies.

Advertisement