twine ren'py alternative game engine that supports multithreading?

Looking for an alternative that is similar to twine or ren’py that supports multithreading. Any suggestions are welcome.

Why do you want multithreading for a visual novel?
I don’t see how you could actually utilise any one core with that.

2 Likes

cacluating 1500 - 5000 npcs turn when there is over 7million possible combinations to make up each of their turns requires more than 1 thread to be done in a timely manner.

2 Likes

Few 2d engines support mulithreads for the scripting components. However, python has mulithreading built in. JS(for twine) can do it but is not built in I don’t think.
You could probably send up a data structure, sent them out to be completed in the threads then process the returned data in the main script thread.
Alternatively unity and unreal have it. Unity’s is an engine feature while unreal has it built into c++.
You may even benefit from doing it as a compute shader on the gpu. CPUs at most have 8 logical core. Sometimes 16 but that’s on above most. Any more threads than the max will not give further benefits. GPUs are a bit more limited but have way more parallel processes.

1 Like

Ok, well I was hoping someone would have an answer other than unity or unreal engine, seeing as with those theres a lot that twine and renpy already have built that i would be rebuilding.

Also unless my understanding is mitaken, while python has a mutithreading library the GIL(Global interpreter lock) prevents making any good use of it, and renpy just flat out does not support it. Javascript has no multithreading im aware of(mainly because browsers don’t support it/need it).

Multithreading in JS is hardly impossible, just something that’s not commonly noticed by the end user. I know nodeJS supports it, anyways.

Regardless, I would argue that your problem is more likely related to how your code is written and optimized than it is about how many threads are available. Even if you have 7.5 million options for each NPC, it’s not like you need to be checking each possibility. Even with extra steps for weighting certain options, you’re essentially picking six random numbers per NPC and running the outcomes of those. If those outcomes require more complex calculations than a couple quick variable changes, then you’ll run into speed issues there too.

I haven’t taken a look at your game, but I would first question why you need complex simulations for thousands of NPCs; that’s far more than a player will ever meaningfully interact with, and if you’re storing them all, you’re creating a massive, unwieldy data structure that’s likely going to cause problems somewhere down the line no matter what you do.

Ren’py and Twine are tools designed to make simple, choice-driven narratives; while pulling in extra Python code might help in the former’s case, neither are designed to handle complex data structures or heavy loads of math.

3 Likes

ok but nodejs is a server side js implementation of js and it uses the v8 core which is a single thread per process js interpreter. so my understanding is its implementation is closer to multiprocessing than multithreading. This is all irrelevant however because nodejs is for running js outside the browser. and twine runs in the browser which’s sandboxes tabs or websites to a single process.

Have you considered ink script. What’s so powerful is that you can create threads of text choices that fold back into a main thread.

1 Like

Ren’py is just python so you have threads there. twine you could run whatever you needed on a background worker if you set it up in the script init part.

So, you don’t really need an alternative to do that! But also, don’t worry about 11 mill calculations. computers can do that. modern CPUs are at like 63GFLOPS now. try it and then if it is slow you tools have the ability to do this. (you could also just split it over a few frames so the game doesn’t lag up. no threading needed)

for the ignorant out there trying to tell me to uses threads in python, please learn and become un ignorant Understanding the Python GIL - YouTube (the example in the first 2 minuts really demonstrate the point)
Also even if using threading library in python was effective, renpy does’t work with it. Renpy has its own interpreter running on top of the python interpreter. hows that gonna work with threads.

Necro to save others from this problem.

Renpy can execute python code, even calling Windows API with ctypes works: Python Statements — Ren'Py Documentation

As for multithreading, it’s impossible to do in Python and JS the way zdeerzz wants. However, it’s possible to use multiprocessing instead. Another method is simply writing a program in another language like C++ that will start the Renpy game on another thread/core while it does all the calculations. The only issue for both is that you need to play around with pointers and other methods that allows the two programs to communicate.

1 Like

This is not even possible in Ren’py!!! Remember that Ren’py uses a lot of modified modules to best suit Ren’py s needs. Multiprocessing module is one such modified module. For all intents and purposes it has been borked in Ren’py. And no, you can’t simply re import it.

A lot of this is intentional on the part of Ren’py to allow compatibility with the Ren’py interpreter and probably most specifically rollback. You would not be able to rollback across another process.

Multithreading in a VN engine was is funny/cursed that it inspired me to learn renpy to do it lol: matiastorres / renpy-multithreaded-example · GitLab

I used the native module approach from above.

Also I think multiprocessing is gonna get added to renpy soon, so you wouldn’t even need to switch languages. Might as well switch languages though since you need the perf already.

Ummmm, im scratching my head, can you explain how the above example is using multiple threads, and those threads are not subject to the python gil!

As far as i can tell using a roundabout way of importing a python module written in python using a native.dll that uses ctypes with no reference to the thread module in either the renpy scrip or the module in the dll, does not magicaly mean your using threads. Renpy script imports your module in the dll and ctypes then proceeds to use it. As far as i can tell it would not get around the python gil, and if it did use threads, the module imported via the dll would still be subject to the gil, and thats ignoring that it does inot even import threads for use.

Where did Ren’PyTom (original, main, and for the most part sole developer of Ren’Py) say this? His stance on mutithreading and multiprocessing have been fairly firm and clear for a while now in regards to Ren’Py, that these are not supported and he does not intend to support them for a while now see github issue for recent example of him shooting it down. Also consider that Ren’Py just made it the python 3 from 2 over a deacade after python 3 released. This is not even considering where not even on the newest version of 3.

I’ll try to explain everything as completely as possible.

Only Python code is subject to the GIL. If the threads don’t use Python, they avoid the GIL. Part of the reason why Python native modules are so prevalent honestly, you can just shell out to C++ for the bits that need to be multithreaded/fast. Native Python extensions/modules have the ability to release the GIL to allow other Python threads to run.

Technically though, I believe calling a ctypes wrapped function does not release the GIL, though multiple threads are spawned/used for calculations as you can see. Creating a “real” native Python extension module will give you access to APIs that let you release the GIL while you execute native code, but I don’t think that will help much beyond the ctypes version if you aren’t using multiple Python threads somehow.

As for how threads are used, I use ctypes to wrap a function. I give it a task in the form of a board state, team, and whether threads should be used. If threads should not be used, it uses a serial version. Otherwise, it spawns up to 9 threads to perform minimax on each possible move (yes this is horrible, but it is simple to implement, and multithreading in a VN engine is far worse imo). After all the threads finish, the result is combined and returned. See native/src/lib.rs, that’s where all this happens.

The GIL is used to ensure that only one Python thread is utilized at a time. This makes synchronization easier and single-threaded code run faster, as you wouldn’t need to screw around with per-object locks or anything of that nature. You also wouldn’t have to worry about race conditions (in Python code), so its easier to program too. However, the single lock means that only one thread in the entire process can run Python code at a time (even between unrelated interpreters, lol). This is starting to be alleviated with PEP 554, but I don’t think that’s ready yet.

Check the README. I linked some sources. The links show him claiming to add support for multiprocessing and the exact commit which added multiprocessing to Renpy. I think its even already in Renpy 8.

I didn’t use multiprocessing mostly because I felt that using a native module would make more sense; if one wants the performance (via multithreading), might was well go all the way. It’s real multithreading too, not multiprocessing.

ok yes i do see this now however, see bellow

Ok but all python and modules for python written in python or c are allsubject to the gil, because If any module written in c or python spawn a thread from the process of the python program in question, the pyhton gil is the manager of all threads for that process and blocks all but one at a time. The python gil is given domain over all the threads in a python process(program) be it from the python written part or otherwise. What language the module is written in does not change that it is all part of one program, and is one process.

Ok the link about size concerns is a github issue about someone complaing that a random module openai’s dependancys are not included bundled by default. This has nothing to do with multithreading or multprocessing, nor does it stop you from adding it to your game folder for distrobution and importing it still.

Another link is really the same github page as the last at a later reply only numpy was mentioned being possibly added in the future

the last is link to a github commit of a localization file where multiprocessing is being included. This is not new or an indication that it will be fully functional as futures from multiprocessing is used by renpy.

It’s a pretty common practice to write native modules to bypass the GIL. As I mentioned earlier, there is even an API (with examples) for doing so: Initialization, Finalization, and Threads — Python 3.11.4 documentation

The GIL doesn’t really manage anything, it just protects the interpreter state from data races. You need to manually release and acquire it from native code in order to avoid corrupting stuff. It’s more opt-in than you think.

For a little more insight: the GIL only applies for Python threads, which are different from lower-level operating system threads. Since only one Python thread can be executed at a time, at most only one OS thread can execute Python at a time. However, the reverse is not true: any number of OS threads can run at once, since the GIL only applies to Python threads.

Native modules can take advantage of this. When you call into a native module, you can convert all Python datatypes into types that no longer require the use of the Python interpreter. Then you can safely release the GIL, so that another Python thread can execute at the same time. When the native module is done executing, it can try to reacquire the GIL. When it has the GIL, it can safely use the Python interpreter APIs again and return its results, then resume the execution of the Python thread it suspended.

Multithreading with Python threads is (currently) impossible due to the GIL, but multithreading with OS threads is definitely possible.

For my first link I tried to link by comment, but you can also just look through that thread. That guy was complaining about a missing stdlib component, like multiprocessing. This issue culminated with more modules being shipped with Renpy by default, which is shown by my second link.

That second link isn’t about localization at all, though the encoding standard library component is related. That second link is a commit that changes the default included standard library components, including multiprocessing among other libraries for Renpy builds.

If you don’t believe me, you can also just try it yourself: I can confirm multiprocessing is working on Renpy 8.1.1.