Faster Array looping #352
Replies: 27 comments
-
Posted at 2015-03-14 by DrAzzy I assume you've implemented some method that you call to send the graphics to the screen? In that case, why the triple buffer? Can you post all your code? |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-14 by @allObjects Yep... is all slow... so think about a different arrangement, like getting things in parallel out... set/get pixel is a slow thing, see LCD Graphics. Some users resorted to inline assembler... As @drazzy mentioned, you would need to provide a little bit more details... about HW involved and SW you wrote... Choosing typed arrays and array buffers makes it already faster because you deal just w/ bytes and not with integers. Plain arrays are resource monsters. Furthermore, try to use Array.forEach... and pass a function name, then variable resolution happens for certain things to much lesser degree... Since Espruino runs of the source code - not like browsers who do some JIT and run from internalized/'compiled'/tokenized code - fast So you do not just have to be frugal w/ code cycles, but also with memory: Since you crate your array, did you think of just having one bit for a byte in a UInt8Array? JavaScript allows you to shift, and that saves you tons of memory (which you will use later for many other things...). First though you can start with a UInt8Array where you put 0x00 and 0x01. Then you loop through it. For your setPixel() I assume it's monochrome what you do... What is the driver/module you use to connect to your matrix display? If your graphics can be drawn with lines, it may be done faster too. I tried some stuff where the SPI is the lock down... But it can made to work. @gordon mentioned also the way to push out a bit image/sprite... and 16 by 8 is small... I though assume you have larger sprites you plan to push out. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-15 by Owen I'm coding Tetris. The code below is unfinished and still very rough around the edges with lots of improvements to be made. Here is what I've done so far.
The next part I'm working on is storing the fallen blocks in an Array so I can render them on each frame and then detect when other falling blocks need to settle on top of them. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-15 by @allObjects Nice! So each pix is a square of a tetris shape, correct? So think of an undraw and redraw... then, at worst, it is 8 pix you set... vs 144... so you get ~93% faster or have to draw only ~6.6%... and if you have transition functions for each shape - average 3 for rotation and 3 for falling down you get it 98% faster, that's down to about 4.2%. Since drawing can be seen as line px set, a line drawing, a line drawing and a px set, and two line drawings at max, you may get down to 4 interactions between source execution state and internal execution state... When designing the pac man, I simplified the man with two lines in an wider and a narrower angle... which means: 2 line un-draws - drawing the current state/lines in background (black) color - and 2 line draws - drawing the new state in foreground (yellow) color. In other words, you still need the board for collision detect - and may be for an occasional redraw after a game break and display of something else... I did not look much into the collision detection algorithm, but any location > 0 (1... are colors) means a free spot... and so you would also treat the border this way, then you do not need extra logic for border detection... in other words, your border is just an other 'occupying color', one you consider stealth / not to be seen/drawn... this increases of course your memory footprint for the board from 144 to 180 - 25%, but you reduce amount of execution logic and complexity considerably... I have not published yet the drawing of the pac man board... but I had to resort to all kinds of 'short cuts' to fit in memory... But I implemented and published the 4x4 - 16 (or more) pieces puzzle - which are all time and memory critical. have fun. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-16 by @gfwilliams Just to add, in Espruino, 'normal' arrays are quite slow to index, but Uint8Array/etc are pretty fast. It's not such an issue for small arrays though. Have you seen the performance page? One thing that can really help is to use
to
But as @allObjects suggests, I think drawing only the bits that have changed each frame would be a great help. You could actually have two instances of So then instead of Just an idea that might tidy up your code, but what about changing
You do:
In fact you can tidy it up even more by putting all the blocks into a global array:
That should be a load faster, and it'll use a lot less memory too. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-16 by @allObjects #game #tetris Had the chance to look at the code.... I guess I understand a bit what you do... Did you consider to make each block a class? If you do so, you do not need switching... Think about each block being a thing that understands the following 'calls' or actions:
Until now, we defined all the so called behavior - the actions a block can do when asked by itself or by 'others'. There will be more of those, but for now let it be with the the above 5. Next we need to define what each block needs to know about itself (and others can ask the block about) - the so called state of the block:
So let me give you an example for the simples block - the 'square-1':
As I remember from playing, you have only one piece active - moving - at a time; and the pieces that have settled do not matter anymore, because when they settle they occupy a position of the board (beside the adding scoring points) - correct? With these assumptions, I'm now moving on with defining the board in similar style. Since the board exists only once, there is no need to create class and then an instance of it, like we do for blocks. Therefore, we create a so called singleton instance right away - with state and behavior:
This code needs for sure more things... for example, when a block is taller than two points the free-check get's out of bound... To make the game more interesting, speed can be varied/increased/interval shortened over time... The 'fall all the way' is missing, and of course all the other blocks... For development, I created a simple html page with embedded javascript. Since I do not have the hardware as you have, in the html test I overwrite the drawing methods... to suite the browser environment and canvas drawing. For input, I invoke the board methods from the UI directly. Your input capturing code has to do that in a similar way. I attached the html - just click on it and it will run. And as you can see from the screen shot, it also implements the bar with two squares... To look at the code, just view source in the browser. Thanks for posting... was a great inspiration --- and of course an example with a much higher success potential than may still unfinished pac man... Attachments: |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-16 by @allObjects ...forgot to mention, that in the code some methods are exactly the same for all blocks... this allows to put them 'back into the board logic' rather then making 'super classes' and de-oo the code a bit - for a higher common good. Initially, I expected more 'short cuts' for the individual block types, but it turned out to be pretty uniform... |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-23 by Owen Thanks for the advice guys I've been rewriting all the code and it's much faster now. For the 'fallen' blocks that need to get rendered and checked for collisions I think I can copy and use the Graphics.buffer Array. I can renderer it using Graphics.drawImage() and easily check pixels for collisions by looking in the array (if it's colour is zero, it's empty).
If you run this code the top-left pixel will flash white very quickly and disapear. If you comment out line 4 (m.setRotation(1);) the code work but obviously the bottom-left pixel lights up. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-23 by @gfwilliams I'm not quite sure I understand what you want to do - you want to just copy the 'old' buffer back into I think the issue is that the image buffer itself has a really strange pixel order (because of the If you do just want to restore the old data, try:
That should work, and would be way faster as it just copies the raw data rather than trying to encode/decode it into pixels. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-23 by @allObjects
The 'easily' may be challenged by the really strange pixel order as mentioned by @gordon. Separating logic (model) from display (view) is good and longstanding practice. Anytime later, logic and display can be optimized and enhanced independently... especially the graphics you would like to enhance over time. Furthermore, it will allow you to pick any display... such as a 320x240 262K color display (with touch screen) - ... where shoving around of full screen buffers is just not an option... after all it's 153600 bytes, 1228800 bits, and this over SPI is ... (call it what your imagination likes...)... and it is only with 16 bit color depth... If you plan to go 24 bit - 1.6M colors - and with some LED displays you need an extra intensity byte - it becomes 32 bits per color! Yes, you can take advantage of a combination of Espruino memory Graphics Buffer and Display memory buffer and the knowledge about their inner workings... On the other hand - when you go that into the intrinsics - you can use then graphics controller that sits on the display to do such operations like shuffling around areas... This is much faster: since your block is already displayed, you just give commands to move graphics areas around - with tetris, it is two (2) rectangles at worst (the higher the resolution the more optimized is the amount of command bytes vs the amount of shoved around bits / bytes. I wondered the (rare?) redraw reason... and therefore just drew an empty board - which is in the end just a single rectangle, and then drawing the blocks that have settled and the active one... It may not beat the redraw from a buffer, but since it is rare and not in time critical phase of a game - for example on resume after a break with something else displayed, redrawing time does not matter that much. On the collision detection: when I was thinking about super fast collision detection a week-end ago - I was considering bit operation & instead of any loops with zero/null-checks. All blocks cover only a 4x4 bits area - 16 bits - and this information fits in an UInt16. So every location - 16*8 has a 16 bit value that represents the (combined) occupation information about the z/y location itself and the neighbors... Whit this setup, a collision detection boils down to a single & operation wether a particular x/y location is busy. The only draw back with this is a bit more code when a block settles to aggregate the UInt16s - and of course some memory consumption. Rotate would also be very simple, because it is just replacing one 16 bit value by another one... This will speed up your logic to the point for very very fast playing. Does not speed increase with level? |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-23 by Owen Ok, so using the output buffer array for collision detection is a bad idea. @gordon I get an error when I try to use m.buffer.set()
|
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-23 by @allObjects You need a buffer... but may be not a display/output buffer... but something has to represent the 'board' - 16x8 matrix - which is actually better than fallen/settled blocks. There is no need to check against all fallen blocks. When a block is fallen, the board is updated. Collision detection uses the board and the block's 'next' position - a maximum of four comparisons. Next positions are the next fall position, 90 degree left/right rotated block on same position, or move left/right position. If the next position would be a fall position, the block settles. If it cannot rotate or move left/right because of detected collision, nothing is done. The fallen blocks are kept for an eventual redraw - or - when you do not score when fallen/settled but at the end when game is over. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-24 by Owen I still get an error when trying to copy the buffer even when there is output data in it? Here is the full test code.
|
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-24 by @gfwilliams
It's not a problem at all - just use
Sorry, I should have checked my code. It's because
One more thing - you'll probably want to do |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-24 by Owen Thank Gordon that works perfectly and using getPixel() for collision detection works really well. |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by @allObjects Looks great! |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by alexanderbrevig Awesome stuff! (You see the the piece mirrored by horizontal/x axis. I did this for a clone i did as part of 1st year of my education and let me tell you - it takes a while to get used to it! When you get used to it, it's fun to swap back as well :D [I actually have a bachelors in games programming of all things...]) |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by @gfwilliams Wow, it looks awesome! Are you going to try and package it up into a complete system? It'd look great with a diffuser over the top (I'm pretty surprised RGB-123 don't sell them actually). |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by DrAzzy He's gotta get music out of it too... the original was just one tone at a time though wasn't it? So you could just use one PWM output and just change the frequency.... It looks AWESOME! |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by @gfwilliams
:) If you use: http://www.espruino.com/Making+Music and then try
I just copied the notes off google, but I've messed up the rhythm a bit. If you change the positions of the spaces you should be able to get it to sound ok though :) Also: Simple hack, but if you connect a speaker between two PWM pins then you can get two tones at once :) |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by Owen Looks like it's time for me to find an old buzzer speaker! |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by @allObjects Looking forward to walk through the complete code - including display of current score! For example first at the top in the 'unused' space, and later at the bottom in the 'used' space of the settled blocks. Using the Graphics, it should be 'easy' to do... Regarding the music: For getting the rhythm, interpret the last blank of a sequence of separating blanks differently then the other ones. The last marks the separation and should be very short... the other ones should last as long as the note themselves (plus the length of the separation blank). This way you can get the rhythm right, but the holding of the notes is still missing. To help with that and not ending up with an overly long and cumbersome string, a more sophisticated notation is needed... The same is true for covering the range of an octave and beyond. Btw, the upper/lower case choice is great for getting the sharps and flats - because every flat can be represented by a flat (just don't try to tell that to a real musician...). |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by @gfwilliams As a bit of a hack, you can actually lengthen the note using the character |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by alexanderbrevig I don't really agree with your suggestion about how to write and handle the music :) I think the better way to think about it is that the piece is written in 16/16 (yes yes I know it's technically still 4/4 with 8th notes but I'm trying to get at the point that each note is actually both a note and a pause to get the staccato feel of the original). By counting it in my head as 16/16 I think this would be correct:
Which is a string of 128 characters for the 8 bar song (with 8 notes and 8 pauses in each bar = 8 * (8+8) == 128) I've not tested it but I think it should be close :) |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-25 by @allObjects ...no problem with your suggestion... that would have been one of my approaches, but I did not want to deviate too much from the givens in the Web site referenced in Espruino's doc. The notation there there is easy to read, gives nice visual feed back about pacing - like good sheets do too by equidistant bars - and is easy to interpret by a machine. If we want to take it seriously, we would even have to account for the time the processor does 'switching'... On a totally 'non-musical' occasion - running continuously a stepper motor controlled by Espruino over some time - I notice irregularities in timing behavior, just by listening to the sound (noise). Each step took about 2ms, so it was about 500 Hz - with a very tight, interval driven loop (in this conversation, but driving a bit a larger bipolar OKI OL840 laser printer main stepper). It could have been some internal house keeping that made the (less prioritized) interval stutter. I hope it was not the something in the internal time keeping (that - temporary pitch change - should be not noticeable by human ear). In order to keep the tempo, it would not be sufficient to just make one call ofter the other... the durations would have to be calculated ever so often to stay in the beat. Now thinking about legato, normal accentuation, and staccato - a quite more elaborate approach is required, not just for timing, but also for intensity: 2 analog output pins would have to drive an external driver. The first pin would define the amplitude (or volume), the second the frequency... To not notice the first one and not create a audio-moire, it would have to be at a very high frequency. Would be a nice experiment... and I'm interested how such a simple driver(/mixer?) could look like... Btw, @AlexanderBrevig, could you publish some audio clips in the IoT experimentation, midi pipe organ pedals and summer conversation? |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-26 by Owen I haven't yet had any more time to work on this, but if you want to see the code from the video here it is: https://gist.github.com/owenmcateer/0a57634498a922a09db1 |
Beta Was this translation helpful? Give feedback.
-
Posted at 2015-03-26 by Owen I finished some more of the code and now I have a fully working game of Tetris. |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Posted at 2015-03-14 by Owen
Hello,
I've got an 8x16 LED matrix and I'm using the Graphics module to draw a simple game onto it. I'm using an Array to hold some background pixels with I then loop and use Graphics.setPixel() to output. Everything works fine but looping my array for active pixels seems very slow. So my question is, is there a fast/better way to be doing the following?
Beta Was this translation helpful? Give feedback.
All reactions