Problem resizing miniwindow

Posted by Twisol on Sun 12 Apr 2009 09:02 AM — 11 posts, 27,024 views.

USA #0
MUSHclient version: 4.40
Script provided
Screenies provided


(You might notice that the gridline loops in DrawGrid are hard-set to loop to 23. That's not really my problem per se; if I fixed that, the real bug would just be hidden one way but not another. It's key to the explanation below)


I have an odd bug here involving miniwindow sizes and drawing. A little backstory might help: I'm writing a plugin that parses the output of a MAP command into a miniwindow. The MAP output can come in multiple sizes (it shows more or less area around you depending on its set radius). The miniwindow should resize itself based on that MAP RADIUS value so it doesn't have a bunch of blank space within the borders.

This plugin is still being worked on, so I'm starting the plugin off at the maximum size, a 5-radius map. Here's an image, with gridlines: http://img17.imageshack.us/img17/1313/bug1rsr.png

That looks right, doesn't it? But then I set MAP RADIUS 4, and the script responds with a call to SizeWindow (see below). I expect the entire drawing region I reserved before to be cleared and deallocated, just like the backbuffer. Oddly enough, that's not so. Here's another screenshot of the miniwindow when I do that: http://img17.imageshack.us/img17/4637/bug2v.png

The grid lines go beyond the area I've newly reserved. And if I change the gridline code to deliberately go past even the radius 5 area, they won't do that. They really do stop right there, way past where they should. And if I deliberately expand the map beyond the original radius 5 window, it gets cut off there. It's as though the first (well, second if you count the dummy window for font loading) size I set determines the drawing area from then on, whether I resize the miniwindow or not.

If I'm wrong somehow please let me know, because this has me stumped. I really want to be able to resize my miniwindow without worrying that the drawing area won't resize with it.

EDIT: Also, just to see what would happen, I added WindowDelete() into SizeWindow. Quite apart from the loss of my loaded font (which I might've been able to put up with), this problem is still there...


Window = {
  new = function(name, x, y)
    local o = {}
    setmetatable(o, Window)
    Window.__index = Window

    o.name = name
    o.x = x
    o.y = y

    -- Dummy window to load font
    WindowCreate(o.name, 1, 1, 1, 1, 6, 2, 0x000000)
    WindowFont(o.name, "f", "Lucida Console", 11, false, false, false, false, 1, 0)

    o.textwidth = WindowTextWidth(o.name, "f", "#")
    o.textheight = WindowFontInfo(o.name, "f",  1)

    -- a grid of 23 cells, with room to make up for the borders
    o.width = o.textwidth*23 + 3
    o.height = o.textheight*23 + 2

    -- set up the miniwindow dimensions here
    o:SizeWindow(o.width, o.height)
    return o
  end,

  SizeWindow = function(self, width, height)
    self.width = width
    self.height = height

    WindowCreate(self.name, self.x, self.y, width, height, 6, 2, 0x000000)
    WindowShow(self.name, true)
  end,

  DrawGrid = function(self, grid)
    -- blanks the miniwindow
    self:ClearGrid()

    -- Draws gridlines
    for i = 1, 23 do
      WindowLine(self.name, 0, i*self.textheight, self.width, i*self.textheight, 0x666666, 0, 1)
    end
    for i = 1, 23 do
      WindowLine(self.name, 3+i*self.textwidth, 0, 3+i*self.textwidth, self.height, 0x666666, 0, 1)
    end

    -- write each line of the map
    for i = 1, 23 do
      self:DrawLine(grid or {}, i-1)
    end
  end,
}
Amended on Sun 12 Apr 2009 09:22 AM by Twisol
Netherlands #1
You aren't helping by giving so little information. Next time, a bit of code that readily works and is stripped down from things that it does not barely need. However, I think I found what is bugging your script. :) [disclaimer] All of this assumes your 'map radius 4' code does the drawing totally seperate from resizing (different triggers etc). If that's not the case, you'll need to give far more info and preferable a working sample for me to debug it with. [/disclaimer]

There are no issues with backbuffers or whatever not being released. The only 'problem' that exists is that MUSHclient does not update the world window - simply because it does not know this needs to be done.

Drawing on a miniwindow does not give any signal of 'redraw the window please'. It cannot do this efficiently, because miniwindows can't know what you plan to do with them. Some miniwindows are used as buffers for various graphical effects, whereas other miniwindows are built up with more than a 100 commands. Just have a look - your example already throws out 48 commands to draw the grid alone. If you check the documentation, it never claims these calls update the screen.

So that brings the question to: why does it work most of the time? Because usually, you call the WindowShow() function together with the drawing code which signals 'oh we have a miniwindow to draw, situation changed, update please'. It -signals- rather than does the drawing. In other words, your entire script get to do its drawing on the miniwindow, and then MUSHclient will notice it needs to draw stuff.. so it draws, and everything you have drawn on the miniwindow shows up too.

So how can you solve it?

Take the following adjusted code: (Untested but yeah.)
Window = {
  new = function(name, x, y)
    local o = {}
    setmetatable(o, Window)
    Window.__index = Window

    o.name = name
    o.x = x
    o.y = y
    o.visible = true

    -- Dummy window to load font
    WindowCreate(o.name, 1, 1, 1, 1, 6, 2, 0x444444)
    WindowFont(o.name, "f", "Lucida Console", 11, false, false, false, false, 1, 0)

    o.textwidth = WindowTextWidth(o.name, "f", "#")
    o.textheight = WindowFontInfo(o.name, "f",  1)

    -- a grid of 23 cells, with room to make up for the borders
    o.width = o.textwidth*23 + 3
    o.height = o.textheight*23 + 2

    -- set up the miniwindow dimensions here
    o:SizeWindow(o.width, o.height)
    return o
  end,

  SizeWindow = function(self, width, height)
    self.width = width
    self.height = height
    
    WindowCreate(self.name, self.x, self.y, width, height, 6, 2, 0x000000)
    self:Invalidate()
  end,

  Invalidate = function(self)
    -- Signal MUSHclient to update.
    WindowShow(self.name, self.visible)
  end
  
  DrawGrid = function(self, grid)

    -- Draws gridlines
    for i = 1, 23 do
      WindowLine(self.name, 0, i*self.textheight, self.width, i*self.textheight, 0x666666, 0, 1)
    end
    for i = 1, 23 do
      WindowLine(self.name, 3+i*self.textwidth, 0, 3+i*self.textwidth, self.height, 0x666666, 0, 1)
    end

    -- write each line of the map
    for i = 1, 23 do
      self:DrawLine(grid or {}, i-1)
    end
    
    -- Done drawing. Let MUSHclient know to update.
    self:Invalidate()
  end,
}


If you end up calling tons of your own methods like this, this method might slightly suffer from many WindowShow() calls. You could probably add a special flag which is checkled in Invalidate(), and toggle the flag like in the following code: BeginWindowUpdate() ...other drawing goes here... EndWindowUpdate() but that's really a bit of premature optimization on my part. :)

And once again: if I get your problem wrong to start with.. give me more information please. :)
Amended on Sun 12 Apr 2009 02:39 PM by Worstje
USA #2
Quote:
You aren't helping by giving so little information.

I'm sorry, I thought what I gave (relevant code, screenshots, explanation) would be enough. I'll supply a plugin below. Thanks for replying, I'll answer each point separately. :)

Quote:
All of this assumes your 'map radius 4' code does the drawing totally seperate from resizing (different triggers etc). If that's not the case, you'll need to give far more info and preferable a working sample for me to debug it with.

Before I call the resizing routine, I clear the screen, which I suppose is a form of drawing. I'm not entirely sure how to relate this to the plugin though, but you can check it out below.

EDIT: Actually, I wasn't clearing the screen, so I added that in. Still doesn't work properly (not that I expected it to, because nothing about the symptoms tells me it's a problem with clearing the drawing area before re-drawing)

Quote:
There are no issues with backbuffers or whatever not being released. The only 'problem' that exists is that MUSHclient does not update the world window - simply because it does not know this needs to be done.

I never claimed it was a backbuffer problem, I claimed it was a - for lack of a better word - a frontbuffer problem.

Quote:
Drawing on a miniwindow does not give any signal of 'redraw the window please'. It cannot do this efficiently, because miniwindows can't know what you plan to do with them. Some miniwindows are used as buffers for various graphical effects, whereas other miniwindows are built up with more than a 100 commands. Just have a look - your example already throws out 48 commands to draw the grid alone. If you check the documentation, it never claims these calls update the screen.

I never assume they do update the screen. I assume the window is drawn every time the output screen is, which the documentation supported last time I checked. Delayed screen update isn't the issue, and the lines aren't some kind of left-overs from before the window was resized.

Quote:
Why does it work most of the time?

It doesn't, really. It only "works" when the size stays the same over the course of the plugin's run. If I make it smaller, everything inside draws smaller, but it seems that it can still draw past the new "edges" into the older, bigger area. And if I make it bigger, everything tries to draw bigger, but it's still locked to the older, relatively smaller area.

It's not a problem with clearing the window or anything. If you look at the second screenshot, those lines were freshly drawn after I changed the size. You'll notice that the lower-right part of the area is completely blank, it doesn't show any signs of a non-cleared drawing area. And as I said, if I resize it larger than the original radius 5 size, the window is clipped by the original size, even though it's drawing within the new parameters.

Quote:
Because usually, you call the WindowShow() function together with the drawing code which signals 'oh we have a miniwindow to draw, situation changed, update please'.

I was under the impression that miniwindows re-drew every time the output window did?




Here's the plugin I've been working with: http://jonathan.com/MapWindowDebug.xml

To reproduce the problems, use MAP RADIUS 4 and then MAP PARSE (it doesn't particularly need valid MAP input to display the problem, but this updates it anyways). Your map will appear like my second screenshot. Now if you do MAP RADIUS 6 and MAP PARSE, the borders (which are based on the window width/height, unlike the gridlines which I just put there to debug with) act like they're trying to draw past the original 5-radius area, but it can't, even though we've re-created the window with a radius of 6.


EDIT: I changed the code so that it would update the MAP when you changed the radius. It still has this problem, not that I expected a lot anyways. =/
Amended on Sun 12 Apr 2009 07:36 PM by Twisol
Australia Forum Administrator #3
Quote:

If you end up calling tons of your own methods like this, this method might slightly suffer from many WindowShow() calls.


The simple approach is to call Redraw () because that schedules a window update for the output window. Calling it lots of times is not particularly inefficient, because there is still only one redraw scheduled for the next time through the main event loop.

Quote:

I was under the impression that miniwindows re-drew every time the output window did?


Yes they do.

Quote:

I have an odd bug here involving miniwindow sizes and drawing ...


I can't reproduce the bug because when running the plugin it doesn't draw the map - I presume I have to be on the MUD for that part to work?

Anyway, there is no issue with resizing windows, for example this test code:


WindowCreate("test", 100, 50, 400, 300, 6, 2, ColourNameToRGB "blue")
WindowShow ("test")

WindowCreate("test", 100, 50, 300, 200, 6, 2, ColourNameToRGB "yellow")
WindowShow ("test")


If you comment out the second WindowCreate you see it creates a nice big blue box, and then putting it back in, you see the smaller yellow box. No overlap or anything like that.

I can't really explain what you are seeing except that by saying the code is doing something you aren't expecting. For instance, you may have more than one window, and you now see a smaller one on top of a larger one. Try looking at the help for WindowList, there is a Lua code snippet you can run to list all existing miniwindows.


Quote:

But then I set MAP RADIUS 4, and the script responds with a call to SizeWindow (see below).


No it doesn't. In the plugin you supplied, SizeWindow is only called from the new function and nowhere else.
USA #4
Quote:
No it doesn't. In the plugin you supplied, SizeWindow is only called from the new function and nowhere else.


Feh. You're completely right and I'm 100% an idiot. I was supposed to call that in the (word == "radius") check in MapAlias. That was causing the problem: it wasn't actually changing the size.

Thanks. >_<
Amended on Sun 12 Apr 2009 11:34 PM by Twisol
Netherlands #5
Haha. At least you see why it is important you give all relevant code ;)

Nick Gammon: The reason I used the WindowShow method was because I thought MUSHclient could optimize that to an InvalidateRect() kind of call, and would as such not need to draw the entire window all over. Minimal optimizations I guess, but it felt neater this way. =)
Australia Forum Administrator #6
Both Redraw and WindowShow call InvalidateRect (indirectly anyway). The only difference is that WindowShow also turns on/off the "show" flag for that miniwindow.
Amended on Mon 13 Apr 2009 10:24 AM by Nick Gammon
Netherlands #7
Ah. I meant the rect to be more precise so it only involves the miniwindow with the WindowShow call, and by passing a flag that was already visible it seemed like a useful optimization.
Australia Forum Administrator #8
Ah I see. Such fine grained optimizations have gone out the window now.

I used to try to invalidate individual lines but that became pretty problematic as under certain circumstances the invalidation was not comprehensive enough.

Now, Redraw invalidates the whole client rectangle, as does WindowShow, as does receiving any new text from the MUD.

However with fast PCs, redrawing a whole window full of text (in itself, not a particularly slow process), is so fast you don't notice it.

I should point out that Invalidate itself doesn't directly result in a redraw. Thus, if you receive 20 lines in quick succession from the MUD, then processing them is a high priority. They may all call Invalidate, but that doesn't mean any drawing has taken place.

The operating system calls the Draw function as a low-priority task (basically when the program is idle), if windows have been invalidated, which is when the drawing actually takes place.
Netherlands #9
Yeah, I'm aware of how invalidation works by actually posting a message in the message queue and such. Although I didn't realize for a moment that Redraw() was just another invalidate - other languages actually use commands named such for forced, immediate redraws. *grin*

On a sidenote.. I've noticed in the past that (admittedly when using non-Lua languages and thus using COM marshalling) there is a relatively heavy penalty for calling world. functions. I can't recall what prompted me to test it since it was several years back, but if performance is really important when using such languages it can pay off to minimize those calls.

Australia Forum Administrator #10
Yes, well MUSHclient now has Repaint to do that, as Redraw was already taken. ;)