flame
flame copied to clipboard
feat: FlameIsolate - a neat way of handling threads
Description
Adding a bridge library for integral_isolates adding support for components to run CPU intensive code with a function similar to Flutter's compute function, but with a long lived isolate. Lifecycle is handled by the game loop, where the isolate would live between onMount and onRemove.
Checklist
- [x] The title of my PR starts with a Conventional Commit prefix (
fix:
,feat:
,docs:
etc). - [x] I have read the Contributor Guide and followed the process outlined for submitting PRs.
- [ ] I have updated/added tests for ALL new/updated/fixed functionality.
- [x] I have updated/added relevant documentation in
docs
and added dartdoc comments with///
. - [ ] I have updated/added relevant examples in
examples
.- Im intending to add example in an upcoming PR
Breaking Change
- [ ] Yes, this is a breaking change.
- [x] No, this is not a breaking change.
Related Issues
Tests are not yet added, but I'm intending on adding some tests for this, hence the Draft status :)
If my understanding is correct, this PR adds the FlameIsolated
component mixin, which essentially adds 2 lines of code:
isolated = Isolated(strategy)..init();
...
isolated.dispose();
Then, there are several questions:
- Is this actually worth it creating an entire new package? (which would require some level support in writing documentation, tests, fixing bugs, answering questions on Discord, etc)
- Does the
FlameIsolated
mixin promote a good programming practice? I imagine some users might decide to add it to their components, and accidentally create a quagmire of threads all running in the background and slowing down the main app (or violating some kind of policy on the number of threads that can be created). - I would imagine the best practice for dealing with long-running computationally intensive tasks would be to have a dedicated thread (isolate) at the app level (i.e. attached to the game instance). This would mean creating an
Isolated
in game'sonLoad
, and then make it globally available through thegame
reference.
It seems to me like this could be a simple documentation recipe, instead of a full-blown package.
I'm sorry to interrupt you conversation 🙃
Isolates might be useful for heavy calculations, yes, but it is extrimely useful when computing not just a function, but preserves state between computations. For example, moving collision detection into separate process. Or calculate player's route by "a star algorithm". But passing every time whole set of data to long running isolate is very expensive, you will spend all possible performance optimisation for type conversion and copying data.
So I might to be mistaken, but it seems this package has this problem. There is no much use of solution like simple compose
function as we already have one 🤷🏼♂️ The thing might be much more useful is something more specific to game process, a "background service" with internal state, just receives small updates from main process and sends result in response.
. But passing every time whole set of data to long running isolate is very expensive
This is not entirely true - or at least on the roadmap last I read it. They were planning on letting you send objects over a port and have it "Transfer" ownership without copying. I'd love to see some numbers on it though.
E.g.: https://api.dart.dev/stable/2.18.1/dart-isolate/TransferableTypedData-class.html
Anyway you will spend your time for type conversions. So it is better to exchange as small amount of data as possible. For example, to send component's position to isolate, I need to convert it to Vector2 from NotifyingVector2. And this means copying the value into new object. If I would have thousands of objects....
@st-pasha that is, so far, correct. How it will evolve in the future I cannot say right now.
- In regards to having thin wrapper packages. While I agree it adds some more maintenance work, most of it will be covered by the underlying package.
- It is always possible to do stupid things. Just because this wrapper (or the underlying package) allows for easily creating isolates without thinking too much about it, does not mean that you can document it properly and explain how you should go about using it. To be honest, I think you could go as much bananas with just adding too many rays in raycasting/tracing, or physics objects. The documentation for this library, as well as the underlying one, is being created and I'll try to make sure to explain how to use it, and the potential pitfalls. Possibly with a few examples.
- I believe there's a few ways of thinking about best practice for this package. Attaching the isolate to the game instance would mean that everyone that uses the isolate through the game reference would share the same job queue. I would personally use an isolate per calculation type. Like one for controlling NPCs (figuring out where to go, what to do and path finding), one for world generation if it is an endless game and so on.
Regarding if it should just be a documentation recipe, and not a full blown package, I leave that up to the Flame team. Maybe @spydon has any wise words?
Regarding all this talk about sending data being expensive. Yes there is a cost with copying data between isolates. This package inherently has the same downsides as with all isolates in Dart. However if you downsize (copy) your data into smaller objects, the cost of sending it is not that significant. And thousands of objects should most likely be fine to copy, CPUs are quite fast nowadays. And as @jtmcdole said, if the data type you copy to is a typed data of some sorts, you can transfer ownership and skip the second copy between threads.
Regarding if it should just be a documentation recipe, and not a full blown package, I leave that up to the Flame team. Maybe @spydon has any wise words?
I don't think that the wrapper being thin at the moment is a big problem, it's still a good convenience mixin for the user. And it will also be easier for us to help debugging it when people have problems since they will all follow the same structure.
The documentation for this library, as well as the underlying one, is being created and I'll try to make sure to explain how to use it, and the potential pitfalls. Possibly with a few examples.
Sounds good, this will also make it easier for the user not to fall in any of the common pitfalls that they could have fallen into if this was not a package.
Does the FlameIsolated mixin promote a good programming practice? I imagine some users might decide to add it to their components and accidentally create a quagmire of threads
We shouldn't force our more advanced users to use worse patterns just because new users can use them to make mistakes. I think it makes more sense to have this on a component level than a game level if one would want different types of tasks, just like @lohnn describes.
What one could do is turn this into its completely separate Component
instead of being a mixin, then it should be harder for users to accidentally add a lot of them since they wouldn't extend from that component in the same extent (hopefully...). But that wouldn't be as user friendly as the mixin is, so I would vote for keeping it as a mixin.