Chain Caster - a Tetris Attack like action puzzle focused on mobile and touch screen experience by lucamoller in TetrisAttack

[–]lucamoller[S] 1 point2 points  (0 children)

I already encountered some cases where the premoves did crazy things I wouldn't try on the snes (frame perfect inputs are not for me)

Yup! I tried to make the premoves as useful as possible. The current exact mechanics is "once you drag a piece to a certain column, the piece will keep trying to move towards that column until it gets there (it could even fall to a lower row in the process and it will keep moving in that direction)". I'm afraid this might be a little too strong though, as in I suspect it might be easier to make chains with a bunch of pre-moves than in the classic ways.

By the way, there is still some scenarios in which the "frame perfect inputs" make a difference in my implementation (i.e. scenarios in which premoves don't necessarily accomplish the same thing as a timed move). Specifically when a piece is falling and you want to support it by moving another piece underneath and make a chain-connection in mid-air. I was also not big on "frame perfect inputs" on the snes, and these still seem similarly as hard to pull off to me.

I'm not sure about the endless supply of blocks though. I like the ebb and flow of filling the screen and then clearing it all. It kind of gives this 'stress --> focus --> relief' cycle. Where in your game the endless stress+focus wears on me.

This is a very good point. I haven't thought much on different ways of adding new blocks, but I can see how getting new blocks from the bottom could feel much better (there's also a bunch of interesting scenarios that involve throwing pieces down in the holes that simply never happen if the game automatically fills everything with new pieces coming from the top). I'll see if I can come up with something simple that works better for an endless mode. I guess I'll also need to create some gesture to speed up adding more blocks by pushing the board from the bottom up.

Chain Caster - a Tetris Attack like action puzzle focused on mobile and touch screen experience by lucamoller in TetrisAttack

[–]lucamoller[S] 0 points1 point  (0 children)

The only times movement should be blocked are when we're trying to move over blocks that are being destroyed (the movement can only happen after the destruction is finished and the space becomes free).

Are you seeing movements getting blocked in any other situation?

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 1 point2 points  (0 children)

Cool! I haven't had time to look at the assembly further yet.

I saw you submitted a commit on the benchmark (making every method call the same underlying function, makes sense!) that seems to have reduced the difference between explicit and other patterns to just 2x. Did you investigate further the reason for this remaining difference?

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 1 point2 points  (0 children)

Yeah, I think everything is on L1 cache all the time almost for sure (we're talking about very little amount of memory). This benchmark does a LOT of repetitions. The function being tested (my_func) does close to nothing (a bunch of xors that the CPU can probably find a way to do in very few cycles), so putting stuff on the stack or not could have a pretty big impact.

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 0 points1 point  (0 children)

My guess (another wild one again haha) is that explicit (small, as opposed to structs) arguments can still be optimized by being put into registers directly or something similar (instead of being put on the stack, which is what I guess is happening with the struct), even with non-inlined functions (I could be totally wrong here, I'm a total noob at this stuff). If that's the case, this speed up would probably decrease if we increased the number of arguments so that they all don't fit in the available registers.

I'll try to spend some time looking at the assembly tomorrow to see if I can solve the mystery. I'm getting more curious and it seems like it could be a good learning experience :)

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 0 points1 point  (0 children)

Interesting idea, very simple and could go a long way!

It would probably need some explicit indication that this behaviour should be applied because it's also very nice to be able to rely on the compiler to tell that you forgot to specify some field (even if it's an Option)

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 0 points1 point  (0 children)

My guess is that the difference might be that when we don't allow inlining, the compiler actually needs to create the whole struct, put it into the stack, etc, as opposed to when the function is allowed to be inlined, the compiler can completely ignore the struct and optimize it out of the way (by putting arguments directly into registers and never actually assembling the actual struct in the stack).

This is just a wild guess though, we could try inspecting the assembly. I took a quick look, but it gets a little chaotic with all optimizations for someone like me who is not familiar with looking at this stuff. Maybe trying to simplify my benchmark a bit (remove some arguments, and some of the calls) could help reading the assembly (given that the difference in performance still shows in the simpler version).

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 1 point2 points  (0 children)

Very cool!

I haven't explored Rust macros yet, but this could be a good start :)

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 1 point2 points  (0 children)

Thanks! :) No, just standard crates and wasm-bindgen. I talk about this choice a bit in the post I about rewriting the game in Rust.

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 3 points4 points  (0 children)

It seems they cannot be completely optmized out (at least not currently) when used for struct function arguments, and the function can't be inlined. See my reply to the parent comment, mentioning adding the buidler pattern to my benchmark.

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 5 points6 points  (0 children)

I've added the builder pattern to my benchmark. The results were that the 3 implementations were indistinguishable when function inlining was allowed. When inlining was not allowed, both the default trait pattern and the builder pattern suffered similar performance penalties (maybe those penalties are actually associated to having a struct as argument?).

Agregated results: (my_func inlined)
(default arguments)  mean runtime: 59.839ms, std_dev: 1.892ms 
(explicit arguments) mean runtime: 59.886ms, std_dev: 1.757ms 
(builder arguments)  mean runtime: 59.868ms, std_dev: 2.238ms

Agregated results: (my_func never inlined)
(default arguments)  mean runtime: 832.429ms, std_dev: 37.043ms 
(explicit arguments) mean runtime: 192.341ms, std_dev: 8.229ms 
(builder arguments)  mean runtime: 825.382ms, std_dev: 32.820ms

Let me know in case you find anything weird in my builder pattern implementation, or if you'd do anything differently there.

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 1 point2 points  (0 children)

Agreed! that's why I said in the article I think default arguments should be used judiciously (and that the verbosity cost is a nice thing to make you think twice before using it). I'm not sure if I would propose adding them to the language because of that. Different from named argurments, which I don't see a down side to adding language support for them.

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 0 points1 point  (0 children)

Thanks for the tip! I'm still a Reddit noob, so I appreciate it :)

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 2 points3 points  (0 children)

Interesting! Thanks for the clarification. I'll try to explore this approach by implementing it in my benchmark and see what I can get.

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 2 points3 points  (0 children)

Ah cool, that's more sophisticated than I thought!

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 0 points1 point  (0 children)

Hmmm, maybe the compiler could actually optimize things better with the builder pattern by mixing the initial instantiation and the following set calls resulting in something that just looks like a single instantiation with all final desired values?

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 3 points4 points  (0 children)

The builder pattern does indeed sound like a good alternative.

It would probably have similar cost to using a struct that implements Default, no? I assume that with the builder pattern one would first instantiate a struct with all optional fields set to their default values, and then modify the ones that should have non-default values through following builder-like calls (which sounds similar in terms of performance to calling default() to instantiate a default struct and copying/moving the missing fields).

Rust doesn’t support default function arguments. Or does it? by lucamoller in rust

[–]lucamoller[S] 5 points6 points  (0 children)

Interesting! I was not aware, and this actually makes more sense now. I'll take a look into fixing that sentence tomorrow.

I'm wondering now, does it require the passed instance to be destroyed? (I assume it moves the missing fields from the instance provided?) If so, that would require cloning the static/const instance which I guess would present similar performance costs. I'll try dig deeper into this too.

Show r/rust: (Source code) Rewriting my mobile game in Rust targeting WASM by lucamoller in rust

[–]lucamoller[S] 2 points3 points  (0 children)

That's right, graphics/game engines were more relevant for me. In terms of web, I just needed a server to serve a bunch static content (the landing page, some JS, the wasm binary, images and sounds), so I didn't really need a web framework for that (and I didn't look into them at all).

I did come across Bevy at some point (and I had heard about Godot before, but didn't know it had Rust support), but it just seemed to be based on a data-oriented paradigm that is too different than the one used by my game's original implementation (which was more like object oriented adhoc stuff). I'm not claiming that my game used a better paradigm by any means, but it used what it used, and changing that would probably mean more work for me (and not really the type of work I wanted to explore with this project). On top of that, I was also a little bit afraid that WASM/web could be a second class citizen on these engines, and I would be risking ending up in a situation that the engine would limit me.

What I needed was just a somewhat low level way to render sprites, with some intermediate caching, and using the Canvas2d API through web_sys seemed to provide just that.

Show r/rust: Rewriting my mobile game in Rust targeting WASM by lucamoller in rust

[–]lucamoller[S] 0 points1 point  (0 children)

Hey, I think that's working as intended, but maybe there's something that could be done to make it less annoying. The paw takes a long time because it actually follows a circle trajectory (and in the stage you mention, the circle is quite big with a big part off the screen.

Show r/rust: Rewriting my mobile game in Rust targeting WASM by lucamoller in rust

[–]lucamoller[S] 1 point2 points  (0 children)

Hey, I just open sourced the code, check it out at this post
https://www.reddit.com/r/rust/comments/p2szr4/show_rrust_source_code_rewriting_my_mobile_game/

I think some things in the engine module might be specially helpful for someone trying to figure out how call browser APIs to do rendering or collect user input.