• Another Frame.js question

    From Kirkman@VERT/GUARDIAN to All on Tuesday, June 09, 2015 15:29:46
    To Deuce or echicken or whoever,

    Lately I've been playing with using frames like tiles. In a 132x60 terminal, I create an 8x3 grid of frames. Each frame is 16x16 characters. This dimension is convenient because it lets me adapt 16x16 pixel RPG sprites and tiles into ANSI.

    Anyway, back to the point. I've got the tile map working. I can move a sprite around it. But the latency when moving a sprite around the map is pretty high.

    The routine works like this:

    * Figure out where you are on the master map and store those coordinates.

    * Using those coords, iterate over the frame grid and check tile types.

    - If a frame needs the same tile as it had before, don't update

    - If the frame needs a new tile, .load() it, then .draw()

    * After iterating over all frames, .cycle() the parent frame


    Checking to see if the tile is the same as before helps some with the latency, but I have a feeling there's a way to reduce the latency much more.

    Many of my tiles have repeated information. For example, there are a lot of solid green blocks since they are grassy.

    When I .load() a new ANS or BIN file into a frame, does Frame.js just replace all data and redraw every character in the entire frame? Or does it check the new data against the old data to see if there are characters it can skip repainting?


    --Josh


    BTW - I posted a video showing an early version of this tile map idea here:

    http://breakintochat.com/blog/2015/05/26/ansi-tile-map/

    ////--------------------------------------------------
    BiC -=- http://breakintochat.com -=- bbs wiki and blog

    ---
    þ Synchronet
  • From Kirkman@VERT/GUARDIAN to All on Wednesday, June 10, 2015 15:59:49
    When I .load() a new ANS or BIN file into a frame, does Frame.js just replace >all data and redraw every character in the entire frame? Or does it check the >new data against the old data to see if there are characters it can skip >repainting?

    Okay, after some testing, I am pretty sure that frame.load() will require every character in the frame to be redrawn.

    I cooked up my own routine to work around this. I set up a frame that serves as a temporary holder. When I'm updating all the tile frames, I no longer .load() the data straight into each tile frame. Instead, I load the tile graphic into the holder frame. Then I iterate over the holder frame and compare each character with the corresponding character in the target tile frame. If they are different, then I .setData() on that character in the tile frame. Once all this is finished, .cycle() the entire screen.

    I wrote some debug code to measure how long each screen redraw was taking. Before my optimizations, the average screen refresh was about 0.67 seconds. After the optimizations, they are down to 0.23 seconds. So it has made a pretty big difference.

    Here's the code if anyone cares:

    // Get the name of this tile type's BIN file
    var tileFile = this._map[mY][mX].getFile();
    // empty the tileHolder
    this._tileHolder.invalidate();
    // Load new tile graphic into tile holder
    this._tileHolder.load(js.exec_dir '/tiles/' tileFile, this._mapTileW, this._mapTileH);
    // Iterate over all characters in this frame
    for (var a=0; a<this._mapTileW; a ) {
    for (var b=0; b<this._mapTileH; b ) {
    var newChar = this._tileHolder.getData(a,b);
    var oldChar = this._tiles[x][y].getData(a,b);
    if ( newChar.attr !== oldChar.attr || newChar.ch !== oldChar.ch) {
    this._tiles[x][y].clearData(a,b);
    this._tiles[x][y].setData(a,b,newChar.ch,newChar.attr);
    }
    }
    }
    // mark this tile for updating
    this._tiles[x][y].cycle();

    ---
    þ Synchronet
  • From echicken@VERT/ECBBS to Kirkman on Thursday, June 11, 2015 00:53:26
    Re: Another Frame.js question
    By: Kirkman to All on Tue Jun 09 2015 15:29:46

    - If the frame needs a new tile, .load() it, then .draw()

    When I .load() a new ANS or BIN file into a frame, does Frame.js just replace all data and redraw every character in the entire frame? Or does

    I'd have to check to be sure, but I suspect this is the case.

    it check the new data against the old data to see if there are characters it can skip repainting?

    The .load() process will slow things down somewhat, and I think that .draw() will force a total redraw of the frame. This is probably where a good chunk of the slowness is coming from.

    How many different kinds of tiles are there? If each is saved as an individual .bin or .ans, you might try combining them all into a single spritesheet. Load the entire spritesheet into each 16x16 frame, then scroll the frame to different x/y offsets as needed to reveal the desired portion of the larger graphic. This would cut out the .load() and .draw() calls, as .cycle() (which I assume is being called regularly on the overall parent frame) should take care of redrawing all child frames as needed.

    ---
    echicken
    electronic chicken bbs - bbs.electronicchicken.com - 416-273-7230
    þ Synchronet þ electronic chicken bbs - bbs.electronicchicken.com
  • From echicken@VERT/ECBBS to Kirkman on Thursday, June 11, 2015 00:56:53
    Re: Another Frame.js question
    By: Kirkman to All on Wed Jun 10 2015 15:59:49

    Before my optimizations, the average screen refresh was about 0.67 seconds. After the optimizations, they are down to 0.23 seconds. So it has made a pretty big difference.

    That's a good improvement. You might still try the spritesheet method I described previously and see if it helps at all - or makes things worse. :D

    ---
    echicken
    electronic chicken bbs - bbs.electronicchicken.com - 416-273-7230
    þ Synchronet þ electronic chicken bbs - bbs.electronicchicken.com
  • From Kirkman@VERT/GUARDIAN to echicken on Wednesday, October 14, 2015 11:10:18
    Re: Another Frame.js question
    By: Kirkman to All on Tue Jun 09 2015 15:29:46

    How many different kinds of tiles are there? If each is saved as an individual
    .bin or .ans, you might try combining them all into a single spritesheet. Load
    the entire spritesheet into each 16x16 frame, then scroll the frame to >different x/y offsets as needed to reveal the desired portion of the larger >graphic. This would cut out the .load() and .draw() calls, as .cycle() (which >I assume is being called regularly on the overall parent frame) should take >care of redrawing all child frames as needed.

    I finally got around to trying your suggested tileset approach, and I ran into a a couple roadblocks. Let me explain.

    I'm taking a 132 char x 60 char canvas and dividing it into an 8x3 grid of frames. Each frame holds a 16 char x 16 char tile.

    In my test code, I'm iterating over each frame and .load()ing my tileset. The tileset is 128x112. I have tried various versions of this tileset, as an ANSI file, a BIN file, with SAUCE or without. No matter what, I always run out of memory.

    Additionally, when I try to debug the errors, Frame.js is giving the wrong data dimensions for my tileset files. As I said, the tileset is 128x112, but if I call .data_width and .data_height after .load()ing, I get 17x832 for the .ANS version or 129x112 for the BIN.

    I don't *think* I'm asking too much of the system memory-wise, but maybe I'm wrong. If you have a minute could you take a look and tell me if you have any thoughts on the out-of-memory issue, or the wrong data dimensions?

    My test code is here: https://gist.github.com/Kirkman/0853cad3deaad373f000#file-1tileset-test-js

    The out-of-memory errors can be seen here (They are slightly different depending on whether you load the ANS or BIN version of the tileset):
    https://gist.github.com/Kirkman/0853cad3deaad373f000#file-2errors-txt

    The debugging info is here: https://gist.github.com/Kirkman/0853cad3deaad373f000#file-3debugging-txt

    You can download the ANS and BIN version of the tileset here: http://breakintochat.com/files/misc/debug/16x16_tileset.ans http://breakintochat.com/files/misc/debug/16x16_tileset.bin

    --Josh

    ---
    þ Synchronet
  • From Digital Man@VERT to Kirkman on Wednesday, October 14, 2015 22:10:13
    Re: Another Frame.js question
    By: Kirkman to echicken on Wed Oct 14 2015 11:10 am

    Re: Another Frame.js question
    By: Kirkman to All on Tue Jun 09 2015 15:29:46

    How many different kinds of tiles are there? If each is saved as an individual
    .bin or .ans, you might try combining them all into a single spritesheet. Load
    the entire spritesheet into each 16x16 frame, then scroll the frame to >different x/y offsets as needed to reveal the desired portion of the larger >graphic. This would cut out the .load() and .draw() calls, as .cycle() (which >I assume is being called regularly on the overall parent frame) should take >care of redrawing all child frames as needed.

    I finally got around to trying your suggested tileset approach, and I ran into a a couple roadblocks. Let me explain.

    I'm taking a 132 char x 60 char canvas and dividing it into an 8x3 grid of frames. Each frame holds a 16 char x 16 char tile.

    In my test code, I'm iterating over each frame and .load()ing my tileset. The tileset is 128x112. I have tried various versions of this tileset, as an ANSI file, a BIN file, with SAUCE or without. No matter what, I always run out of memory.

    Additionally, when I try to debug the errors, Frame.js is giving the wrong data dimensions for my tileset files. As I said, the tileset is 128x112, but if I call .data_width and .data_height after .load()ing, I get 17x832 for the .ANS version or 129x112 for the BIN.

    I don't *think* I'm asking too much of the system memory-wise, but maybe I'm wrong. If you have a minute could you take a look and tell me if you have any thoughts on the out-of-memory issue, or the wrong data dimensions?

    I don't really know anything about frame.js (except vaguely that it exists), so I can't really help specifically with that, but regarding the memory usage: you can allocate more memory to the JS runtime via your configuration (JavaScriptMaxBytes in http://wiki.synchro.net/config:sbbs.ini#global). The defualt is 8Mbytes, which logically, should be enough.

    If a script is performing a lot of string manipulation, it might need to use the new(is) js.flatten_string() method to reduce unnecessary memory consumption.

    Other methods of scope manipulation can encourage garbage collection. You can read/print the js.gc_attempts property to make sure garbage collection is running and possibly manipulate js.gc_interval to make it happen more often. It's certainly possible that there is a bug in the current code in regards to JS memory usage/garbage collection.

    digital man

    Synchronet "Real Fact" #24:
    The Digital Dynamics company ceased day-to-day opperations in late 1995.
    Norco, CA WX: 75.1øF, 72.0% humidity, 3 mph SE wind, 0.00 inches rain/24hrs

    ---
    þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net
  • From echicken@VERT/ECBBS to Kirkman on Thursday, October 15, 2015 00:47:27
    Re: Another Frame.js question
    By: Kirkman to echicken on Wed Oct 14 2015 11:10:18

    I don't *think* I'm asking too much of the system memory-wise, but maybe I'm wrong. If you have a minute could you take a look and tell me if you have any thoughts on the out-of-memory issue, or the wrong data dimensions?

    By default, Synchronet places some tight limits on how much memory can be used by javascript modules. See JavaScriptMaxBytes and JavaScriptContextStack in sbbs.ini. I believe that the idea here is to save people from their own (inadvertently) memory-hungry code. These values are fairly reasonable for more typical BBS modules.

    If you find that you actually need to allow JS modules to use more memory, you can bump these values up. On a test BBS, I got around the 'Out of memory' error when running your script by bumping the ContextStack value up to 512K from the default 16K. (That's quite a jump, but then again on my main BBS I have these values set higher, I don't recall by how much.) Some rough math suggests that if every character cell in your 128x112 graphic was occupied, and repeated over 24 frames, you might need more than that. The overhead of the Frame objects would need to be allowed for as well.

    Additionally, when I try to debug the errors, Frame.js is giving the wrong data dimensions for my tileset files. As I said, the tileset is 128x112, but if I call .data_width and .data_height after .load()ing, I get 17x832 for the .ANS version or 129x112 for the BIN.

    I generally use .BIN files for loading into frames. The data_width of 129 in this case suggests that something is slightly off (by one) in the way that Frame is reporting this. At a glance, it looks like Frame is adding 1 to this value ... for reasons I do not know. I'll ask mcmlxxix if he remembers why it's doing this. For now, just subtract 1 from your data_width, and that's the real number.

    The data_width of 17 for the .ANS file would be a two-part issue. Again, data_width would actually be 16 in this case, which is the width of the frame in question. Apparently when loading a .ANS file, Frame wraps the loaded content to the width of the frame. I can't imagine why, but maybe there's a reason - so I won't change it just yet and will just suggest that you use .BIN instead.

    ---
    echicken
    electronic chicken bbs - bbs.electronicchicken.com - 416-273-7230
    þ Synchronet þ electronic chicken bbs - bbs.electronicchicken.com
  • From Kirkman@VERT/GUARDIAN to echicken on Thursday, October 15, 2015 09:58:23
    If you find that you actually need to allow JS modules to use more memory, you >can bump these values up. On a test BBS, I got around the 'Out of memory' >error when running your script by bumping the ContextStack value up to 512K >from the default 16K. (That's quite a jump, but then again on my main BBS I >have these values set higher, I don't recall by how much.) Some rough math >suggests that if every character cell in your 128x112 graphic was occupied, and
    repeated over 24 frames, you might need more than that. The overhead of the >Frame objects would need to be allowed for as well.

    Thanks, I didn't realize these settings were configurable in SBBS. I think I will try to stick to Synch's default settings for memory.

    It occurs to me that I can combine my frame "re-painting" method with the tilesheet method: Make one invisible "holder" frame that loads the entire tileset at the beginning. Then on each redraw, copy data out of the holder to paint those characters that have changed in the visible frames. By loading only one copy of the tileset, I should have no memory issues, and I'll also get the benefit of no longer constantly loading individual tile BIN files.

    I generally use .BIN files for loading into frames. The data_width of 129 in >this case suggests that something is slightly off (by one) in the way that >Frame is reporting this. At a glance, it looks like Frame is adding 1 to this >value ... for reasons I do not know.

    The data_width of 17 for the .ANS file would be a two-part issue. Again, >data_width would actually be 16 in this case, which is the width of the frame >in question. Apparently when loading a .ANS file, Frame wraps the loaded >content to the width of the frame.

    I thought data_width and _height properties were supposed to be the true dimensions of the loaded image, since you can use offset to scroll that image around in the frame. But, yeah, until that's sorted, I'll stick to BINs.

    --Josh

    ---
    þ Synchronet
  • From echicken@VERT/ECBBS to Kirkman on Thursday, October 15, 2015 14:38:22
    I thought data_width and _height properties were supposed to be the true dimensions of the loaded image, since you can use offset to scroll that image around in the frame. But, yeah, until that's sorted, I'll stick to BINs.

    Yes, that's what they are supposed to be. The problem is that Frame is returning is internal cursor to the start of the line once it reaches the viewable width of its canvas, regardless of whether the image it's loading told it to do so (with a crlf or a positioning sequence, say.) Data that should be getting set outside of the viewable area is instead being moved down to the next line, and wrapping on and on like that (which explains the high number you're seeing for data_height.)

    ---
    echicken
    electronic chicken bbs - bbs.electronicchicken.com - 416-273-7230

    ---
    þ Synchronet þ electronic chicken bbs - bbs.electronicchicken.com