-
Notifications
You must be signed in to change notification settings - Fork 118
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Forceloading is excessive #277
Comments
This sound like an interesting idea! I'm not so sure about the performance of VoxelManip and if it is suitable for this purpose. We are using forceloading for several reasons: First of all, it can be more performant to keep mapblocks in memory instead of loading them every time a signal passes by. But most importantly, not being able to trace back a wire to all of its sources can cause serious bugs: If we have two onstate receptors connected to a long wire at opposite ends, turning off only one of them shouldn't turn off the wire. If we can't trace back the whole wire quickly, we cannot act accordingly. It sounds like it should be possible to use VoxelManip for this purpose. Just make sure that this doesn't impact performance. Especially in singleplayer it is more important for mesecons to function correctly and also be fast than to save server resources. So all in all, I would be willing to try out an implementation based on VoxelManip 👍 |
Do you have any guidelines for how to figure out what the performance impact is? I have a fairly fast computer, and I don’t personally have any large mesecons networks set up in my own worlds. Is there a sample world with a big machine in it somewhere I could download? |
You'll get nodes and p1/p2 from vmanips, but no node metadata. If you can do everything with those bits, it's pure Lua and it'll be very fast, much faster than any |
@Hawk777 For testing out performance, a very long wire (>300 nodes) with a switch on one end and a large light sign or something at the other end should be enough. Then make sure, it doesn't only work if the whole area is loaded, but also works if you just restarted the game. Make sure that it correctly handles two switches connected to it at large distance, that when turning of one switch while keeping on the other will keep the light sign on. @sofar That sounds really good, mesecons doesn't use node metadata, so this sounds like it could positively impact performance in general! I just wasn't aware that VManips could be used in this way. |
Actually, my original thought was rather simpler: Certainly using a VM for everything may be faster, only this would be rather easier to implement I think. |
I'm not sure if this will load the area, but it sounds like it. However, that is sort of dependent on the implementation, we can't be sure that a future minetest version won't do things differently. So if possible, I'd prefer directly reading from the VoxelManip map instead of calling I think it is best to wrap all that logic up in a simple |
It looks like the current implementation of Another question came to mind: this could result in signals going so much further than before, because VMs are not limited the way forceloading is. Should we have a cap on how far a signal can go before we just stop and say “OK, that’s enough, do the rest on the next tick via the actionqueue”? Otherwise a sufficiently perverse person could build a giant signal wire and force the server to try and load gigabytes of data from disk in a single tick. |
Yes, that is what I was thinking, don't use I wouldn't limit wire lengths just now. If someone wanted to make the server lag, there are propably better ways to achive that right now (like just having a large farm of luacontrollers with interrupts at maximum frequency and powering large mesecon wire structures or something). |
Well, Also, from inspecting the source code, it’s not clear to me that keeping a VM around from one tick to the next is a good idea. As far as I can tell, after calling |
Yes, the VM contents will need to be discarded every tick (or every time a new signal propagation starts), I think we are on the same page there. Not caching at all would mean calling Since effectors (like pistons etc. that could actually change the map) are currently only activated one globalstep after the signal has reached them (using the actionqueue), what would need to be done is basically performing an iterative (!) breadth-first search looking for linking (by their connection rules) conductors from given starting point while at the same time loading all the VManips for the affected blocks. The on signal is simple: After having found the whole network of conductors that connects to the starting point, you have a list of conductors and a list of effectors that will need to be activated. You can then replace all the conductors and effectors in the VM and write your changes to the map. The off signal is more difficult: While performing the breadth-first search, you should try and see if any onstate receptors are connected to the conductors and abort the search at that point (the wire stays on!). If no receptors were found, continue to replace all the conductors and deactivate receptors in the VM, and write changes to the map. |
The one problem I can see is that you have limited information about how much data to load in the vmanip - you don't know from the start of the cable where the end is, or how the cable runs. If you solve that problem, you could actually have two endpoints properly update hundreds of nodes apart, and only update the cabling in between as blocks become active. It would of course be possible to store endpoint data in metadata, and cables themselves would have to store all the endpoints as well so updates can be efficient. Then again, that approach uses nodemeta and really doesn't need to use vmanips, and perhaps I'm overseeing something entirely as well.... |
@sofar, I think this is touching on a bigger, more general question of how Mesecons handles circuits. On the one end is pretty much what we have today, where the only information about circuit topology is stored in the world map itself, and this ticket is just a question of improving performance while walking through that data. On the other end, taking your idea and fully generalizing it, is that we store some sort of abstract schematic representation of the circuit in an auxiliary data structure. A way to do this would be to allocate what the electronic engineering world would call equipotential nodes, i.e. all the wire reachable by a search starting from a particular point. Roughly equivalent to “one wire”, except that forks, loops, etc. are also included. As wires are placed and dug, this auxiliary data structure is kept up to date. Turning receptors on and off just updates the state of a single equipotential node, which instantly tells you all the actuators connected to it and lets you notify them. Wires on the ground have their equipotential node ID stored as metadata, and they just visually always reflect the state of their associated equipotential node. In Minetest terms, you would just swap all the wires currently loaded to their new state, and then each wire would, on load, check whether it should be on or off and swap itself if needed, but you wouldn’t need to touch unloaded map blocks at all. The first option is a fairly simple optimization, while the second would be a significant rewrite. You would have to deal with a lot of complicated issues, like finding a data structure that allows efficient searching but also reasonable cutting and joining performance (for digging and placing wires), while also taking care of ensuring that the data structure always stays up to date with respect to the world map, even in the face of possible errors and unclean shutdowns (think power failures of the Minetest server machine). That’s why I thought of either having each VM load just one mapblock, or perhaps having a small radius as a tuning knob which allows creation of fewer VMs by having each one load, say, n_×_m_×_n mapblocks. Or perhaps _n_×1×1 in the direction the wire is going when it reaches the boundary, under the assumption that most wires that go a long way will do so in a straight line. |
This is the point where this alternative fails. I have also experimented with rewriting the mesecons core in similar fashion (last summer). And while all the problems with data structures, joining and splitting equipotential areas are solvable (and also interesting for programmers), all of this goes wrong as soon as you take into account crashing servers or even just mods that do unexpected things (like worldedit). Above all of that, debugging a system like this with two places storing the same information (the visible map and the equipotential area store) is just a complete nightmare of unreproducible bugs. So, please don't do this to yourself! I think it should make sense to just always load the current MapBlock into a VManip, since the server also thinks in terms of MapBlocks. So it wouldn't improve performance much to load less than the current MapBlock (assuming that passing the data from minetest to lua isn't the bottleneck - that remains to be testesd), and I also don't think loading more than the current mapblock is required in many cases, since most circuits are pretty small. If this turns out to be poorly performant, we could have some fancy prediction (e.g. load close blocks if node is close to the edge of a mapblock, prefer loading horizonal nodes over loading vertical ones, assume straight wires, ...), but I hope this isn't necessary. |
A VM isn’t capable of loading less than a mapblock anyway.
I also agree with this idea; I’ll write up a basic implementation which loads one mapblock per VM and we can add some sort of hinting mechanism later if it becomes necessary. |
Having looked at this in more detail, I’m not sure it’s possible to do without breaking backwards compatibility with other Mesecons-connectable mods. The reason for this is that we call This wouldn’t rule out my original idea of using the VM to prime the mapblock cache and then using |
The associated PR is now merged into master so I think this discussion is probably over. |
This is a bit of a mix of feature request and discussion topic. I’m not sure if people agree or not.
As written, at present, Mesecons forceloads every mapblock connected along a wire to a signal source when the signal changes state. In my opinion, forceloading is a rather large hammer being used against a rather small target. All we care about is tracing through the mapblocks to see where the wire connects to (and to change some nodes), yet to accomplish this, we will burn the global forceloading budget and activate a lot of mapblocks.
What if, instead, in
get_node_force
, we calledget_node_or_nil
first, then, on anil
return, used a voxel manipulator to bring the target mapblock into the cache and simply calledget_node_or_nil
a second time? This would lead to all those mapblocks being loaded but inactive, which should be much cheaper. As long as signal edges occur more frequently than once everyserver_unload_unused_data_timeout
, everything would live in cache permanently and so the initialget_node_or_nil
would succeed and no voxel manipulator would ever be created, but we would not pay the cost of the mapblock being active. For slower signals, the blocks would eventually be unloaded, and subsequent signal edges would reload them via the VM.Thoughts? If people are agreeable, I would be happy to look at implementing this myself and turning this into a PR.
The text was updated successfully, but these errors were encountered: