This mimics sim.walls.* rather than elem.*_PT_*. The latter is fine because element identifiers follow the *_PT_* pattern, but tool identifiers don't, so they'd be a pain to tell apart from the rest of the API.
This was because element tools associated with Lua elements were reallocated every time their properties changed. They are now properly updated instead.
This is still a mess and I don't like it at all.
Don't just set the .type of struct Particle to 0, kids. Code tracked back to the times from before git; already present in b0ea52690ba5, the initial commit.
Reproduce with
sim.clearSim()
elem.property(elem.DEFAULT_PT_DMND, "Update", function(i, x, y)
local ni = sim.photons(x, y)
if ni then
if not sim.partProperty(ni, "vx") then
print(x, y)
assert(false)
end
end
end)
local i = sim.partCreate(-1, 100, 100, elem.DEFAULT_PT_NEUT)
sim.partProperty(i, "vx", 0)
sim.partProperty(i, "vy", 2)
sim.partCreate(-1, 100, 100, elem.DEFAULT_PT_DMND)
sim.partCreate(-1, 100, 102, elem.DEFAULT_PT_PRTI)
sim.paused(true)
sim.framerender(1)
Though if done this way, it's actually a photons corruption.
Render mode checkboxes each control multiple bit flags, but they would fully unset all bits they control, even if they shared flags with other render modes (which they always do). This would cause more things to turn off than intended when deselecting options, and all other checkboxes would appear unchecked. Now it will recalculate render mode based on what's checked instead.
Air display mode checkboxes are supposed to act as radio buttons, but it was letting all air modes be selected at once. They are mutually exclusive because only one display background can be drawn at once. Now, they act as exclusive radio buttons again.
That is, it's not thrown away and rebuilt every time the underlying set of tools changes (the "derivation" approach). Rather, changes to the underlying set are applied to GameModel's list (the "persistent" approach).
Normally, I'd prefer the derivation approach if its overhead is small enough, which in this case it would be. However, this change, among a few others, is done in preparation for exposing tool functionality to Lua. The Lua side will use persistent numeric identifiers to refer to tools, which means at least Lua tools would need to follow the persistent approach, at which point it makes more sense for all tools to do so, than a hybrid approach.
Also uncurse WindTool in the process; it is now an ordinary SimTool. This required extending SimTool with a Drag method that is called every tick while the user prepares for drawing a line and is shown the preview.
Since the Simulation doesn't have to understand SimTools anymore, they don't have to be a part of SimulationData and so NoToolClasses.cpp can finally be removed from the renderer executable.
Somehow foundElements (now renamed to foundParticles) made it into RendererSettings in 820f44a71690, even though it's actually a result of the rendering process. It is now extracted the same way as the resulting frame.
The default directory is acquired via getExternalFilesDir. Typical value: /storage/emulated/0/Android/data/uk.co.powdertoy.tpt/files, which seems to be user-accessible both on old and new Android. Probably. Hopefully. We'll see.
I tried to document everything the advanced search does, but it was mostly a guessing game discovering all its features. It appears to be insanely powerful, supporting AND, OR, negation, parenthesis,quote-encapsulated phrases, and limiting search to only certain fields. These can be chained together as well. I tried giving examples that could possibly be useful.
Terms like before: and after: are not part of advanced search, and thus are global search options even if they're inside parenthesis
That is, only if the Renderer needs to immediately blast off to render another frame. If we run it on the main thread, we can just use its video buffer directly.
"Possible" here means "when there are no particles with Lua graphics functions that haven't reported that they don't want to be called again and there are no beforesimdraw/aftersimdraw event handlers". I put my trust in Simulation::elementCount here; I sure hope it's always correct. If there is any of these, the renderer thread is paused and rendering happens on the main thread. To be clear, these are the situations in which Lua code is called with LuaScriptInterface::eventTraits having the eventTraitSimGraphics bit set.
If the detection of this situation is somehow flawed, there will still be no crashes because when running on the renderer thread, Renderer is told to not call Lua functions, and GameController::BeforeSimDraw/AfterSimDraw is not called by GameView::RendererThread. The worst that can happen is that some particles are not rendered properly.
Renderer settings (color and display modes, deco state, finding-element state, etc.) are managed by GameModel in the form of a RendererSettings and are passed to Renderer before each frame. The RenderableSimulation that Renderer works off of is also configured before each frame, either to point to a copy dedicated to the renderer thread, or directly to GameModel's Simulation, if the renderer thread is paused. Similarly, the result of the rendering is managed by GameView in the form of a RendererFrame, to enable e.g. sampling with deco tools without having to pause the renderer thread.
Each time GameView::OnDraw is called, it checks whether rendering is allowed to happen on a different thread (see above for conditions).
If it is, but the renderer thread is absent, GameView starts it and dispatches it (provides with settings and simulation data) right away. At this point, the renderer thread is definitely rendering a frame. GameView waits for this to finish and exchanges data with the renderer once it's done: it takes the result of the rendering and also dispatches the renderer thread again. This introduces a one-frame delay in rendering, which we can live with.
If rendering is not allowed to happen on a different thread, GameView waits for the renderer thread to finish what it's currently doing and pauses it, then proceeds to render the frame by itself.
Affecting this condition (whether rendering is allowed to happen on a different) by installing or removing event handlers, or clearing graphics cache (which, note, requires acquiring a unique lock on it), while the renderer thread is working, is not an issue because the renderer thread doesn't bother with event handlers, and because it acquires a shared lock on the graphics cache for rendering.
The GameModel's Renderer is thus still primarily managed by GameView, potentially in two different threads, in a strictly non-overlapping manner. The exception to this is when RenderView butts in and starts doing renders of its own; in such cases, the renderer thread must be paused explicitly, as RenderView does by calling GameView::PauseRendererThread.
These were arrays because that made them easy to compose, but that's a UI task, it makes no sense to propagate them internally. That said, RenderView is still confusing because it still has checkboxes that control multiple bits of render mode and display mode at once; I'm not dealing with that for now.
The game now prefers the item-type preferences Renderer.RenderMode and Renderer.DisplayMode over the array-type preferences Renderer.RenderModes and Renderer.DisplayModes. It'll preserve the old ones for compatibility if found and derive the new ones from them. The corresponding Lua functions are now deprecated and will just return single-item arrays from now; the similarly named replacements operate with integers.
More precisely, move them to RenderableSimulation, which Simulation now inherits from. This type is copyable and so is easy to send off to be rendered, potentially on a different thread.
Simulations as big as 2048 by 2048 had been possible, but this change enables sizes such as 1900 by 2100, which would have failed the originally very power-of-2-oriented check. Very useful, no doubt.
Also make the tags button span the entire window, fixing the annoying gap or overlap with other buttons when changing the sim size.
I had been under the impression that .data() on containers returns a const pointer and so hadn't been using it too much. I was wrong; I think that may be limited to std::basic_string. EDIT: I've checked, even std::basic_string has a non-const .data() since C++17, nice.
I got the escaping wrong so on windows, where the path to the script has characters that need to be escaped, the script wouldn't be found, leaving all dependents stale, triggering rebuilds of all resources.
This causes these dependencies to be rebuilt if the script chanages, which is heaps better than having to remember to clean the build directory every time.
So they get copied into the build site even if you invoke only the powder build target, rather than all build targets by not specifying one. Case in point:
meson compile
vs
meson compile powder
wherein the latter will build powder.exe but will not copy the dlls into the build site, which are required by powder.exe.
It's getting triggered because the lifetime of some objects that own RequestHandles are not exactly deterministic, to put it lightly. I'm not dealing with those.
Timing is now such that the input to the gravity process (the point masses and the mask derived from gravity walls) is passed in BeforeSim and the output of the gravity process (the 2D force field) is taken also in BeforeSim, at the same time. This means that input generated by particles in frame n is passed to the gravity process at the beginning of frame n+1, the corresponding output is available by the beginning of frame n+2, and so it is visible to particles in frame n+2. Thus, gravity is now properly and predictably one frame late, though, sadly, it's displayed two frames late.
This is in contrast with the case of not being late at all, which would be the case if input generated in frame n produced visible output by frame n+1, and also in contrast with what we've had until now, which was that gravity was *at least* one frame late, but it could be more out of sync than that if the gravity process for some reason took more time to produce output.
Further, both input and output now make it into snapshots and saves, which fixes one half of what causes the phenomenon wherein beams of PHOT under the effect of gravity wiggle for a few frames before stabilizing after a save is loaded. The other half is that velocities are saved at very a low precision, so overall, the effect of this change on this phenomenon is negligible.
It is not crucial that a client understands this new piece of info, as clients have been fine not getting gravity data from saves for years. Thus, its presence does not restrict the save to client versions newer than the one that adds this feature.
An interesting question this brings up is whether settings, crucially, the enabled state of Newtonian gravity, should be considered simulation state and be also included in saves.
Gravity data is now also included in the output of sim.hash, so Newtonian gravity no longer has to be disabled in order to ensure local determinism.
The diff is large because I gave up on being non-invasive and renamed important structures that lots of elements use. gravp, which was functionally just hypot(gravx, gravy), was removed, because it was only ever used to display a single value in the HUD. I've also taken a few detours and made changes that really should have been in separate commits, see below, but oh well.
This commit also includes a complete rework of the gravity wall area of effect discovery algorithm. The old implementation was extremely broken even beyond the usual C99-isms.
Also fix Simulation.NewtonianGravity being read as an integer from powder.pref, even though it's a boolean and is in fact saved as a boolean. It's pure luck that it's worked fine until now.
Also introduce PlaneBase for use with PlaneAdapter. PlaneBase is a very budget version of std::span from C++20, so budget in fact that it doesn't even store span size; this is fine because PlaneAdapter knows the span size anyway. std::basic_string_view worked for char types but this new code deals with floats.
Also include blockAir data in saves unconditionally. It used to be included only if ensureDeterminism was enabled, like rngState and frameCount, but those last two are conditionally included only because some assumptions are broken if they are included, such as that of expecting a saved simulation to take different paths of evolution after each reload. blockAir has no such effect.
Also remove sim.resetGravityField aka tpt.reset_gravity_field because it was agreed that it's really weird that the output of the gravity process can be changed at all from Lua, not to mention that it can only be reset to zero. No scripts to my knowledge ever used these functions.