• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Blackboard

Level 13
Joined
Nov 7, 2014
Messages
571
Blackboard - a simple way of using multiboards

JASS:
library Blackboard requires PlayerArray

//! novjass

Blackboard - a simple way of using multiboards

General notes:
    This library comes with extarnal commands that change a few values of the
    Game Interface that make multiboards look good/black. The original idea comes
    from user Toadcop at www.wc3c.net, I have simply translated it into vJass and
    Grimoire commands which make importing a bit easier.

    Blackboard uses multiboards with 1 column (thus every multiboard item has the same with as the multiboard itself),
    it does not use icons (MultiboardSetItemIcon(...)),
    it does not use value colors (MultiboardSetItemValueColor(...), one can print a string with a color code of course)
    and it does not require manual multiboard item retrieval (MultiboardGetItem(...) or mb[<row>][<column>]),
    that happens implicitly / transparently from the user while using the print and println methods.

Credits:
    Toadcop - BX-TRS II: http://www.wc3c.net/showthread.php?t=91163

Requirements:
    PlayerArray - http://www.hiveworkshop.com/threads/players.276065/

API:

//
// Getting/Retrieving a player specific Blackboard:
//

local Blackboard bb = Blackboard(0) // Player Red's blackboard
local Blackboard bb2 = Blackboard(PlayingPlayers[1]) // first playing player's blackboard (usually Player Red)


//
// Printing
//

// Prints the string s to the current line without going to the next, i.e print buffers the text.
// One can access that buffer with: bb.buf and concatenate directly to it if they want to
// avoid the extra function call.
//
method print takes string s returns nothing

// Prints the string s and the previously buffered text to the current line and goes to the next, also
// clears the buffer.
// In debug mode if the number of printed lines exceeds the constant MAX_LINES an error is displayed.
//
method println takes string s returns nothing


//
// Showing/Hiding
//

// NOTE: this method must be called first after all the print/println calls
// It takes the width for the Blackboard to have.
// There are predefined widths that can be used:
//
//     static constant real MAX_WIDTH = 0.780
//     static constant real HALF_WIDTH = 0.385
//     static constant real GOLD_ICON_START_WIDTH = 0.325
//     static constant real TRSII_WIDTH = 0.315
//
method flush takes real width returns thistype


// Sets the title of the Blackboard
//
method title takes string s returns thistype

// Shows the Blackboard to the player that was specified when it was retrieved.
//
method show takes nothing returns thistype

// Shows the Blackboard to the players inside the PlayerArray pa.
//
method show_to takes PlayerArray pa returns thistype

// Shows the Blackboard to all players.
//
method show_all takes nothing returns thistype


// Hides the Blackboard from the player that was specified when it was retrieved.
//
method hide takes nothing returns thistype

// Hides the Blackboard from the players inside the PlayerArray pa.
//
method hide_from takes PlayerArray pa returns thistype

// Hides the Blackboard from all players.
//
method hide_all takes nothing returns thistype


//! endnovjass


// Allow the external commands to be executed by saving the map. After that remove the '!',
// close the map and reopen it again.
// The external commands change some Game Interface values so that the multiboards look good/black =).
//
// import "blackboard/external-commands.j"

struct Blackboard extends array
    // the number of rows the multiboard native type can display seems to be 39;
    // you can set this value to something smaller if you know you are never going to
    // write more than that many lines (each line uses 1 row), doing so would save some RAM
    //
    static constant integer MAX_LINES = 39

    // some predefined widths that can be used with the flush method, e.g:
    //    call bb.flush(bb.HALF_WIDTH).show()
    //
    static constant real MAX_WIDTH = 0.780
    static constant real HALF_WIDTH = 0.385
    static constant real GOLD_ICON_START_WIDTH = 0.325
    static constant real TRSII_WIDTH = 0.315

    private static multiboard array mbs
    private static string array lns

    readonly integer p
    readonly integer ln_count
    string buf
    private multiboard selected_mb

    // the players for which we create multiboards
    private static method multiboard_players takes nothing returns PlayerArray
        local PlayerArray result = PlayerArray.create()
        local integer i

        if PlayingPlayers != 0 then
            set i = 1
            loop
                exitwhen i > PlayingPlayers.count
                call result.add(PlayingPlayers[i])
                set i = i + 1
            endloop
        endif

        // if Observers != 0 then
        //     set i = 1
        //     loop
        //         exitwhen i > Observers.count
        //         call result.add(Observers[i])
        //         set i = i + 1
        //     endloop
        // endif

        // whenever we want to show a Blackboard to all players (show_all) we use the
        // local Blackboard bb = Blackboard(15)
        call result.add(15)

        return result
    endmethod

    // multiboards cannot be created correctly during map init time
    //
    private static method lazy_init takes nothing returns nothing
        local Blackboard b
        local multiboard mb
        local integer p
        local integer rows_count
        local integer i
        local PlayerArray pa

        set pa = multiboard_players()
        set i = 1
        loop
            exitwhen i > pa.count
            set p = pa[i]

            set b = Blackboard(p)
            set b.p = p
            // set b.ln_count = 0
            set b.buf = ""

            set rows_count = 1
            loop
                exitwhen rows_count > MAX_LINES

                set mb = CreateMultiboard()

                call MultiboardMinimize(mb, false)
                call MultiboardSetRowCount(mb, rows_count)
                call MultiboardSetColumnCount(mb, 1)
                call MultiboardSetItemsStyle(mb, /*show-values:*/ true, /*show-icons:*/ false)

                set mbs[p * MAX_LINES + rows_count - 1] = mb

                set rows_count = rows_count + 1
            endloop

            set i = i + 1
        endloop

        call pa.destroy()
        call DestroyTimer(GetExpiredTimer())
    endmethod

    private static method onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.0, false, function thistype.lazy_init)
    endmethod

static if DEBUG_MODE then
    private static method panic takes string msg returns nothing
        call BJDebugMsg("|cffFF0000[thistype] error: " + msg + "|r")
        if 1 / 0 == 0 then
        endif
    endmethod
endif

    // NOTE: this method does not inline
    // one can concatenate directly to the this.buf field if they want to avoid
    // the extra function call
    //
    method print takes string s returns nothing
        set this.buf = this.buf + s
    endmethod

    method println takes string s returns nothing
static if DEBUG_MODE then
        if this.ln_count >= MAX_LINES then
            call panic("attempt to println " + I2S(this.ln_count + 1) + " lines but MAX_LINES is " + I2S(MAX_LINES))
        endif
endif

        set lns[this.p * MAX_LINES + this.ln_count] = this.buf + s
        set this.ln_count = this.ln_count + 1
        set this.buf = ""
    endmethod

    method flush takes real width returns thistype
        local multiboard mb
        local multiboarditem mbi
        local integer line_index
        local integer p = this.p
        local integer ln_count = this.ln_count

        if ln_count == 0 then
static if DEBUG_MODE then
            call panic("nothing to flush for Blackboard(" + I2S(this) + ")")
endif
        endif

        set mb = mbs[p * MAX_LINES + ln_count - 1]
        set this.selected_mb = mb

        set line_index = 0
        loop
            exitwhen line_index >= ln_count

            set mbi = MultiboardGetItem(mb, line_index, 0)
            call MultiboardSetItemValue(mbi, lns[p * MAX_LINES + line_index])
            call MultiboardSetItemWidth(mbi, width)
            call MultiboardReleaseItem(mbi)

            set line_index = line_index + 1
        endloop

        set this.ln_count = 0
        set mbi = null

        return this
    endmethod

    // these methods must be called after the flush method because
    // before that we don't know which multiboard would be selected,
    // i.e there could be println(...) calls after title(...)
    //
    method title takes string s returns thistype
        call MultiboardSetTitleText(this.selected_mb, s)
        return this
    endmethod

    method show takes nothing returns thistype
        if LOCAL_PLAYER[this.p] then
            call MultiboardDisplay(this.selected_mb, true)
        endif
        return this
    endmethod

    method show_to takes PlayerArray pa returns thistype
        local integer i = 1
        loop
            exitwhen i > pa.count
            if LOCAL_PLAYER[pa[i]] then
                call MultiboardDisplay(this.selected_mb, true)
            endif
            set i = i + 1
        endloop
        return this
    endmethod

    method show_all takes nothing returns thistype
        call MultiboardDisplay(this.selected_mb, true)
        return this
    endmethod


    method hide takes nothing returns thistype
        if LOCAL_PLAYER[this.p] then
            call MultiboardDisplay(this.selected_mb, false)
        endif
        return this
    endmethod

    method hide_from takes PlayerArray pa returns thistype
        local integer i = 1
        loop
            exitwhen i > pa.count
            if LOCAL_PLAYER[pa[i]] then
                call MultiboardDisplay(this.selected_mb, false)
            endif
            set i = i + 1
        endloop
        return this
    endmethod

    method hide_all takes nothing returns thistype
        call MultiboardDisplay(this.selected_mb, false)
        return this
    endmethod

endstruct

endlibrary

Simple example:
JASS:
    local Blackboard bb = Blackboard(0)
    // local Blackboard bb = Blackboard(PlayingPlayers[1])

    call bb.println("A")
    call bb.print("B")
    call bb.print("C")
    call bb.print("D")
    call bb.println("")
    call bb.println("E")
    call bb.print("F")
    call bb.print("G")
    call bb.print("H")
    call bb.println("I")
    call bb.println("THEND")

    call bb.flush(bb.TRSII_WIDTH).title("|cffFFFFFFBlackboard|r").show()

BBDebugMsg:
JASS:
library BBDebugMsg initializer init requires Blackboard

globals
    private constant integer MAX_LINES = 16 // must be <= Blackboard.MAX_LINES
    private string array lines
    private integer line_p = 1
endglobals

private function init takes nothing returns nothing
    local integer i = 1
    loop
        exitwhen i > MAX_LINES
        set lines[i] = ""
        set i = i + 1
    endloop
endfunction

function BBDebugMsg takes string s returns nothing
    local Blackboard bb = Blackboard(PlayingPlayers[1])
    local integer i
    local integer n

    set lines[line_p] = s
    set line_p = line_p + 1
    if line_p > MAX_LINES then
        set line_p = 1
    endif

    set n = 1
    set i = line_p
    loop
        exitwhen n > MAX_LINES

        call bb.println(lines[i])

        set i = i + 1
        if i > MAX_LINES then
            set i = 1
        endif

        set n = n + 1
    endloop

    call bb.flush(bb.GOLD_ICON_START_WIDTH).show()
endfunction

endlibrary

Note that you can only use Blackboard after the "loading screen ends"
(TimerStart(CreateTimer(), 0.0, false, function init))
because multiboards created during map initialization time don't work correctly.
This means that the following won't work:
JASS:
private static method onInit takes nothing returns nothing
    local Blackboard bb = Blackboard(0)
    call bb.println(...)
    call bb.flush(bb.HALF_WIDTH).show()
endmethod
 

Attachments

  • blackboard-preview.png
    blackboard-preview.png
    194.8 KB · Views: 269
  • blackboard.zip
    7.6 KB · Views: 94
Last edited:
Level 14
Joined
Dec 12, 2012
Messages
1,007
I've stolen the idea and the implementation (pretty much the whole thing yes?).
I've just translated it to vJass and made it somewhat easier to "install" by using
the ConstantMerger and FileImporter (grimext tools) for the game interface changes and the
file importing.

Ok, I'm still not sure about this... at least you will have include credits to the original creator of this code (plus some kind of documentation), but for me it does make sense to pack this into an own library given the huge amount of unorganized code in the map you linked.

I wounder why the following does not work for example?

JASS:
scope MyScope initializer onInit
	private function callback takes nothing returns nothing
		call Blackboard.write_line("One line")
		call Blackboard.show(Player(0), Blackboard.HALF_WIDTH)
		
		call BJDebugMsg("Added one line")
	endfunction
	
	private function onInit takes nothing returns nothing
		call TimerStart(CreateTimer(), 1.0, true, function callback)
	endfunction
endscope

Why is it not possible to just append lines to the Blackboard? The user hardly wants to re-add all lines every time he appends a new one, but this should work automatic like BJDebugMsg.

Also it doesn't really make sense IMO to use a de-facto static class here and search the right "instance" with a loop over all players. Better let the user create an instance of a Blackboard for a specific player (by passing the associated player in the constructor of the blackboard).

Also some more features would be nice. Erase a line by index (or a range of lines), append lines, swap lines etc. would make this more usefull.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I wounder why the following does not work for example?
JASS:
scope MyScope initializer onInit
    private function callback takes nothing returns nothing
        call Blackboard.write_line("One line")
        call Blackboard.show(Player(0), Blackboard.HALF_WIDTH)
       
        call BJDebugMsg("Added one line")
    endfunction
   
    private function onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 1.0, true, function callback)
    endfunction
endscope
Well if you read the script .write_line just writes into an array and .show
dumps that array into a corresponding multiboard and resets the "line counter".

Why is it not possible to just append lines to the Blackboard? The user hardly wants to re-add all lines every time he appends a new one, but this should work automatic like BJDebugMsg .

You could emulate BJDebugMsg with something like this:

JASS:
struct BJDebugMsgBB extends array
    static constant integer MAX_MESSAGES = 16 // must be <= Blackboard.MAX_LINES
    static string array messages
    static integer message_count = 0

    static method bj_debug_msg takes string msg returns multiboard
        local integer i
        local integer n

        set messages[message_count - message_count / MAX_MESSAGES * MAX_MESSAGES] = msg
        set message_count = message_count  + 1

        if message_count < MAX_MESSAGES then
            set i = 0
            loop
                exitwhen i >= message_count
                call Blackboard.write_line(messages[i])
                set i = i + 1
            endloop
            return Blackboard.show(Player(0), Blackboard.HALF_WIDTH)

        else
            set i = message_count - message_count / MAX_MESSAGES * MAX_MESSAGES
            set n = 0
            loop
                exitwhen n >= MAX_MESSAGES
                call Blackboard.write_line(messages[i - i / MAX_MESSAGES * MAX_MESSAGES])
                set i = i + 1
                set n = n + 1
            endloop
            return Blackboard.show(Player(0), Blackboard.HALF_WIDTH)

        endif
    endmethod
endstruct

function bj_debug_msg takes string msg returns nothing
    // Display it to all players.
    call MultiboardDisplay(BJDebugMsgBB.bj_debug_msg(msg), true)
endfunction

scope MyScope initializer onInit
    globals
        integer line_number = 1
    endglobals

    private function callback takes nothing returns nothing
        call bj_debug_msg("line " + I2S(line_number))
        call BJDebugMsg("line " + I2S(line_number))
        set line_number = line_number + 1
    endfunction

    private function onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 1.0, true, function callback)
    endfunction
endscope


Also it doesn't really make sense IMO to use a de-facto static class here and search the right "instance" with a loop over all players.
The right multiboard "instance" is found in constant time, there is no "searching with a loop over all players".
set blackboard = blackboards[GetPlayerId(p) * MAX_LINES + lines_count - 1]

Also some more features would be nice. Erase a line by index (or a range of lines), append lines, swap lines etc. would make this more usefull.
I would like to keep Blackboard as it is, i.e just a translation to vJass of what I saw in BX-TRS II's display module's functionality. Users are free to add features on top of it if they so desire.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Well if you read the script .write_line just writes into an array and .show
dumps that array into a corresponding multiboard and resets the "line counter".

Yes, thats the technical reason, but the question was more about the design rational behind that. Because as I said the user hardly wants to bookkeep and re-add the lines manually. This kind of functionality should be provided by the system itself.

You could emulate BJDebugMsg with something like this:

Then integrate it into the system as "appendLine" or something like that?

The right multiboard "instance" is found in constant time, there is no "searching with a loop over all players".
set blackboard = blackboards[GetPlayerId(p) * MAX_LINES + lines_count - 1]

I meant in the constructor. Is there a reason why the system iterates over a fixed number of players to init the multiboard items? I guess having a instance class that takes the corresponding player in the constructor would be both more efficient and flexible?

I would like to keep Blackboard as it is, i.e just a translation to vJass of what I saw in BX-TRS II's display module's functionality. Users are free to add features on top of it if they so desire.

I think the system has potential, but it needs some improvement, especially in terms of functionality and features. Of course user are always free to extend existing systems, but the whole point of systems is that users get some functionality without having to code most of the features themselves.
 
Level 13
Joined
Nov 7, 2014
Messages
571
I meant in the constructor. Is there a reason why the system iterates over a fixed number of players to init the multiboard items?
I guess having a instance class that takes the corresponding player in the constructor would be both more efficient and flexible?

I think the common case is for maps to treat all players equally meaning that all players are going to be shown a multiboard (at some point),
so that's why all the multiboards for all players are initialized at the start. That said, it is true that if a player slot is computer controlled, not
used or if that player never needs to see a multibored we are wasting some RAM.

I fail to see how making instances of classes that take the corresponding player would be more flexible.
There are always going to be 12 instances (at most, depending on number of players) and the only thing this would
save is having to pass the player parameter, it doesn't seem to be worth it.


Then integrate it into the system as "appendLine" or something like that?

There seems to be 2 modes of displaying lines of text:

1) Showing only a fixed number of lines at a time (trying to display more is considered an error).
This is the mode that BB.write_line and BB.show use.

2) Showing only a fixed number of lines at a time but when trying to display more lines older
lines are replaced with newer in a scrolling fashion, i.e the way DisplayTextMessage/BJDebugMsg work.

In some case one of the modes might be prefered over the other and vice versa.
I can't think of intuitive method names that would reflect that modeness though.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
I think the common case is for maps to treat all players equally meaning that all players are going to be shown a multiboard (at some point),
so that's why all the multiboards for all players are initialized at the start.

Well there might be scenarios where this isn't fullfilled. Something like an attacker/defender map where the map maker (for whatever reasons) only wants to display the blackboard to the defending team for example.

That said, it is true that if a player slot is computer controlled, not
used or if that player never needs to see a multibored we are wasting some RAM.

I fail to see how making instances of classes that take the corresponding player would be more flexible.
There are always going to be 12 instances (at most, depending on number of players) and the only thing this would
save is having to pass the player parameter, it doesn't seem to be worth it.

As you said, its potentially wasting resources. With an instance the user can dynamically choose which player should have a multiboard.


There seems to be 2 modes of displaying lines of text:

1) Showing only a fixed number of lines at a time (trying to display more is considered an error).
This is the mode that BB.write_line and BB.show use.

2) Showing only a fixed number of lines at a time but when trying to display more lines older
lines are replaced with newer in a scrolling fashion, i.e the way DisplayTextMessage/BJDebugMsg work.

In some case one of the modes might be prefered over the other and vice versa.
I can't think of intuitive method names that would reflect that modeness though.

One possibility I can think of would be to have another parameter that determines this behavior:

JASS:
local Blackboard bb = Blackboard.create(Player(0), true) // true means "append mode"
call bb.writeLine("Hello World") // appends the line
set bb.appendOnWrite = false // Change the mode
call bb.writeLine("Done") // Just display, no autoscroll
 
Level 13
Joined
Nov 7, 2014
Messages
571
As you said, its potentially wasting resources. With an instance the user can dynamically choose which player should have a multiboard.
The absolute worst case is for a map to have 12 human players and only the last one to ever need to use BB, which
would lead to a (12 - 1) * 39 = 429 empty multiboards wasted. And lets say that each
empty multiboard is 64 bytes each (I don't know if that's true, it's probably a lot less), so:
429 * 64 = 27456 Bytes = 27.456 Kilobytes = 0.027456 Megabytes.
It seems to me that even in the worst case the amount of "wasted resource" is negligible for
today's hardware.

I thought about the "[auto]scrolling" mode and decided that it's kind of a corner use case.
I mean in what kind of a scenario it would make sense for text to scroll really? Some kind of
a multiboard chat or something? Even then the implementation that would come with the
library itself would certainly not be sufficient (because that would be the dullest chat ever,
I mean no emoji?). So I think it's best for the user to implement his own scrolling and whatever
else logic his map requires.

PS: I don't remember the last time I needed BJDebugMsg's scrolling behaviour,
I mostly use it for "printf-style" debugging to see the execution order, etc
because there's no debugger for jass (as far as I know).
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
Oh man I was off by a factor of ~200x. It turns out that 429 "empty" multiboards initialized by the above scheme (first multiboard's row count = 1, second's row count = 2, etc.) takes around 5-6 Megabytes of RAM.

Yes and those things sum up if we don't take care of it. Its not really a big problem but if we can improve on it with little effort, then why not. But I see you already implemented that.

I thought about the "[auto]scrolling" mode and decided that it's kind of a corner use case.
I mean in what kind of a scenario it would make sense for text to scroll really? Some kind of
a multiboard chat or something?

Some kind of event log for example (like assault mode: team A destroyed the outer wall, team B conquered the warehouse etc.). You don't want to pollute the chat with that but it should be updated and displayed continuously. Its not that hard to imagine valid use cases for such functionality.

I'm not really sure if this really fits into the Jass Resource section in its current state since Jass resources should be rather generic and reusable. At the moment it is a bit lacking on features to be applicable in various different scenarios. Its ok if you keep the system as it is now, but then it would fit better into the Spell section I guess.

If you want to keep it here, some more features would be nice.
 
Level 23
Joined
Apr 16, 2012
Messages
4,041
I mean it is pretty simple, but it could be moderately useful.

write_line should in my opinion call show.

Also, there is no way to show one blackboard to multiple players, which is in my eyes quite big disadvantage. Either implement some form of mirror, or allow it to render for force(write_line_for, or if you dont want write_line to render, showFor, as exmaples)
 
More structuring power would be handy, being able to write/remove line [x]. With it the user could easily decide what methods he wants to add, like pop() or so, or just ignores writing.
(the current method is also not neutral, but works like push, without a way back)

edo494's point to making a board comatible with multiple players is good.

I'm not very sure. Maybe it would make sense to make MAX_LINES a non-static member. But then you can argue with any other member actually, too. :s

Some short lines of docu would be good, for not forcing the user to scroll in code to get all members.

I tested the board, and it looks actually quite good in game.
From my view it should stay in JASS section, and not be moved to Spells Section. Especially the installation is not really gui friendly.
 
It seems good if one wants to mimic BJDebugMsg, as it was maybe the initial thought.
Though it definitly would benefit if it nativly supported more structering functionality.

For example something like this would be very neat:

JASS:
set bb.lines = 10 // define new amount of lines (and board size)

set bb[5] = "Hello"
call bb.add(bb[5] + " Donald") // Adds a new line with "Hello Donald" (11 lines now)

call bb.remove(5) // removes the 5th line -> requires internal loop (10 lines now)

set bb.removeFront() // removes the first (empty) line -> requires internal loop (9 lines now)

for showing/hiding the board to certain player(s) I think just something like this would be best:

JASS:
method operator display= takes boolean b returns nothing
    call MultiboardDisplay(this.selected_mb, b)
endmethod
and the user can do local blocks or what ever he wants when he wants to show/hide only to some players.

Now it is actually also a multiboard system at last. Even a bit specific one -- one column, and the special layout.
Though it should then also keep up with some multiboard system in some sort of useability, over a simple "writeln".
For example I love this style in usage: System - Multiboard | The Helper .
I don't see benefits from using current API over the ones suggested, for example.

If the blackboard should be a DebugOnly multiboard, then it still should also layed out a bit differently...
but I think blackboard should be similar functionality like a multiboard, but with easier usage due to only 1 column.
 
Level 13
Joined
Nov 7, 2014
Messages
571
It seems good if one wants to mimic BJDebugMsg, as it was maybe the initial thought.
To mimic BJDebugMsg one can do this (see first post).


For example something like this would be very neat:
JASS:
set bb.lines = 10 // define new amount of lines (and board size)

set bb[5] = "Hello"
call bb.add(bb[5] + " Donald") // Adds a new line with "Hello Donald" (11 lines now)

call bb.remove(5) // removes the 5th line -> requires internal loop (10 lines now)

set bb.removeFront() // removes the first (empty) line -> requires internal loop (9 lines now)
No, I don't like this. Use the multiboard library you linked instead.


for showing/hiding the board to certain player(s) I think just something like this would be best:
JASS:
method operator display= takes boolean b returns nothing
    call MultiboardDisplay(this.selected_mb, b)
endmethod

I added a show_to, hide_from methods that take a PlayerArray instead.

I don't see benefits from using current API over the ones suggested, for example.
I really like the names I've picked so no more method renaming, I think.

If the blackboard should be a DebugOnly multiboard, then it still should also layed out a bit differently...
but I think blackboard should be similar functionality like a multiboard, but with easier usage due to only 1 column.
Have you played TcX? As far as I know all the multiboards used there are done like this and they look
amazing (kudos Tc).
 
I've seen the video already if I remember correctly, it really looks good! But never played the map.

I believe we need to change the description a bit if it's strictly against common multiboard philosophy (what we talked above).

If I imagine having a board to write, write in next line, and reading current line -- okay. But I can still somehow imagine it might become useful in cases to do operations in a certain line, nativly supported. Maybe not for your wanted purpose, but then please explain/describe it next to " a simple way of using multiboards". At the moment I personaly have the feeling of an avoidable limitation.

Maybe I see it also a bit small-minded, and it actually can exist like this, and someone else can write an addon or so if really needed, that can be used as extension of this basic functionality.
If a third person has an opinon in this, you are welcome to share thoughts.
maybe @KILLCIDE @PurgeandFire @edo494

Good idea to combine the PlayerArray with show/hide functions!

Besides the discussion about functionality it looks good for me, so let's wait a bit if someone else writes here, too.
 
Alright, alright. :s ^^

So if no more 3rd party feedback comes, might you still think of a better description?
It's always very important in my opinion that we can clearly define one's system purpose.

And Blackboard - a simple way of using multiboards is not very accurate due to our discussion, since it's not really trying to mimic normal multiboard behaviour, but is more a certain specialization or so.
Nestharus's library is one for simpler usage of multiboards.
 
Level 13
Joined
Nov 7, 2014
Messages
571
might you still think of a better description?
Nope... =)

It's always very important in my opinion that we can clearly define one's system purpose.
The purpose of any multiboard is to display some information to the players. Blackboard tries to be as easy as possible to use in order to do just that. It uses a single column only (thus every multiboard item is the same width), it doesn't use icons, it doesn't use value colors (one can print a string with a color code of course) and it doesn't require manual multiboard item retrieval (MultiboardGetItem(...) or mb[<row>][<column>]), that happens implicitly / transparently from the user.


PS: I was worried for a moment that my messages count would go from 255 back to 0 (u8 overflow) ;P
 
Last edited:

Deleted member 219079

D

Deleted member 219079

Follow JPAG and this serves as a nice addition to hive's resources.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Added the ability to configure the players for which the multiboards would be created:
private static method multiboard_players takes nothing returns PlayerArray
Attempting to use a Blackboard for a player that is not in that array is of course an error.

Added the convention (which is not enforced / can be ignored by the user) that whenever a Blackboard needs to be displayed to all players (call bb.show_all()) that Blackboard is retrieved from Player(15)'s blackboards via (local Blackboard bb = Blackboard(15)), if instead it is retrieved from a PlayingPlayer's blackboards (e.g: local Blackboard bb = Blackboard(PlayingPlayers[1])) and is shown to all players but then the player from which it was retrieved was shown (call bb.show()) another Blackboard that used the same number of lines as the one shown to all players, then all players would see the blackboard that was shown to the player instead of the one that was shown to all players. I am sorry if this is confusing, I didn't explain it very well =/.
 
The purpose of any multiboard is to display some information to the players. Blackboard tries to be as easy as possible to use in order to do just that. It uses a single column only (thus every multiboard item is the same width), it doesn't use icons, it doesn't use value colors (one can print a string with a color code of course) and it doesn't require manual multiboard item retrieval (
MultiboardGetItem(...)
or
mb[<row>][<column>]
), that happens implicitly / transparently from the user.
^Can we put the explaination into the desccription?

It's nice you provide a debug example.

I don't think it's a big deal as they are only created once on start,
but for completness sake there can be a destroy method, for example when a player leaves.
( and then the local mb/mb item has to be nulled ;) )

What's the meaning of "TRSII"?

As no other input comes,
from my side, then, we have this approve it as it is, once the description is improved.
 
Level 13
Joined
Nov 7, 2014
Messages
571
but for completness sake there can be a destroy method, for example when a player leaves.
( and then the local mb/mb item has to be nulled ;) )
Hm, not much point in doing that... =)

What's the meaning of "TRSII"?
You would have to ask Toadcop to be sure, but my guess is something along the lines of: Toadcop Recipe System version II (2, Diablo II), maybe =)?
 
You would have to ask Toadcop to be sure,
Well, I would have myself no idea what to use it for. If you also don't know, maybe we don't need it?:D

JASS:
set pa = multiboard_players()
        set i = 1
        loop
            exitwhen i > pa.count
            set p = pa[i]

            set b = Blackboard(p)
            set b.p = p
            // set b.ln_count = 0
            set b.buf = ""

            set rows_count = 1
            loop
                exitwhen rows_count > MAX_LINES

                set mb = CreateMultiboard()

                call MultiboardMinimize(mb, false)
                call MultiboardSetRowCount(mb, rows_count)
                call MultiboardSetColumnCount(mb, 1)
                call MultiboardSetItemsStyle(mb, /*show-values:*/ true, /*show-icons:*/ false)

                set mbs[p * MAX_LINES + rows_count - 1] = mb

                set rows_count = rows_count + 1
            endloop

            set i = i + 1
        endloop
Uhm, you are creating default boards for each new line amount?
Intuitivly it seems like a big overhead, and using one board dynamicly in combination with MultiboardSetRowCount seems like a better alternative.
Is there a reason?


JASS:
static constant real MAX_WIDTH = 0.780
static constant real HALF_WIDTH = 0.385
Is half width maybe 0.390 ?

Nice name for the debug write function. ;D
 
Level 13
Joined
Nov 7, 2014
Messages
571
Uhm, you are creating default boards for each new line amount?
Intuitivly it seems like a big overhead, and using one board dynamicly in combination with MultiboardSetRowCount seems like a better alternative.
Is there a reason?
Good question. If I remember correctly it "bugs" if one tries to use a single multiboard and sets its row count to the number of lines and then sets each row's value.
(If we first print 3 lines and show the multiboard, then we print 1 line and show it and finally we print 2 lines and show those, of the 2 lines that we tried to show at the end only the first one is visible, the second appears to be blank).

Besides, the "overhead" happens once, when the map loads.

Is half width maybe 0.390 ?
Try it out. 0.385 seems like a better "half" width to me =).

Nice name for the debug write function. ;D
I know, right ;P?
 
The problem was with SetRowCount native, I found a same issue here [Solved] - Weird Multiboard bug.

So maybe then only one mb can be used, and then I also would not see much sense for the flush method, but the user could be allowed to write next lines again and again
without flushing. And the width then can be also just be set with a new function, and instead of "flush" there can maybe be just a "clear" function, or also "flush" ( just called it "clear" for not to confuse with old function now)

The member "integer p" which refered to the player number was not needed I think, one coudl just use "this" maybe.

Can you look in demo?

Edit:

Try it out. 0.385 seems like a better "half" width to me =).
Ah, there seems to be some margin, so, yes, nice.
And the TRSII_WIDTH values seems actually to be useful, too, it looks like a good width. In the demo I just made it default width.
 

Attachments

  • demo.w3x
    30.9 KB · Views: 82
Level 13
Joined
Nov 7, 2014
Messages
571
So maybe then only one mb can be used, and then I also would not see much sense for the flush method, but the user could be allowed to write next lines again and again
without flushing. And the width then can be also just be set with a new function, and instead of "flush" there can maybe be just a "clear" function, or also "flush" ( just called it "clear" for not to confuse with old function now)
You should try the "3, 1, 2" printing test I described above.

The member "integer p" which refered to the player number was not needed I think, one coudl just use "this" maybe.
Don't really want to overload `this`, it will make things more confusing (for me) to read.

Can you look in demo?
I don't really want to call MultiboardSetRowCount on every println, or in a loop (in the show method, for example), thats why I am sticking to Toadcop's original design of having 1 multiboard per line per player, even if it takes more RAM; this way it avoids that "overhead" (calling MultiboardSetRowCount) during gameplay.
 
Can you write a function that wont work for my demo, so I can direcly test? The problem was that you did not reduce size by 1, liked explained in linked thread.

I don't really want to call MultiboardSetRowCount
on every println
That's a proper way to handle such things imo. It's good I believe to have it dynamic, and reduce/increase lines when needed instead of creating a static set of handles in before.
If it could hold 1000 lines, would you create 1000 boards for each player, or increase just the line count? Only theoretic example, but it makes the philosophy clear.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
Can you write a function that wont work for my demo, so I can direcly test? The problem was that you did not reduce size by 1, liked explained in linked thread.

JASS:
function the_3_1_2_test takes nothing returns nothing
    local Blackboard bb = Blackboard(0)

    call TriggerSleepAction(1.0)
    call bb.width(Blackboard.TRSII_WIDTH).title("|cffFFFFFFBlackboard|r").show()

    call bb.println("1 - first we")
    call bb.println("2 - print")
    call bb.println("3 - 3 lines")

    call TriggerSleepAction(1.0)
    call bb.clear()
    call bb.println("1 - then we print a single line")

    call TriggerSleepAction(1.0)
    call bb.clear()
    call bb.println("1 - and after that")
    call bb.println("2 - we print 2 lines")
endfunction

function do_test takes nothing returns nothing
    call ExecuteFunc("the_3_1_2_test")
endfunction

function init takes nothing returns nothing
    call TimerStart(CreateTimer(), 0.0, false, function do_test)
endfunction

That's a proper way to handle such things imo. It's good I believe to have it dynamic, and reduce/increase lines when needed instead of creating a static set of handles in before.
If it could hold 1000 lines, would you create 1000 boards for each player, or increase just the line count? Only theoretic example, but it makes the philosophy clear.
But they aren't 1000 lines. I don't want it dynamic, even if calling MultiboardSetRowCount one line at a time works great and its dirt cheap (I doubt it), because it will only save a few MB of RAM in the worst (12 players games) case, which is basically nothing.
 
That's an other bug, the line is not blank. The first line gets not properly cleared now somehow. But actually, wow, that's again an other mutltiboard bug.

Seems like it does not work to clear a board and instantly write on it again in the very same frame:

in demo, if you one removes the "TriggerSleepAction(0)" then the bug occurs... :S

demo:
JASS:
library BBDebugMsg initializer init requires Blackboard

globals
    Blackboard bb
endglobals

function Do_Init takes nothing returns nothing
    set bb = Blackboard(0)
    call bb.width(Blackboard.TRSII_WIDTH).title("|cffFFFFFFBlackboard|r").show()
endfunction

function Do_1 takes nothing returns nothing
    call bb.println("1 - first we")
    call bb.println("2 - print")
    call bb.println("3 - 3 lines")
endfunction

function Do_2 takes nothing returns nothing
    call bb.println("1 - then we print a single line")
endfunction

function Do_3 takes nothing returns nothing
    call bb.println("1 - and after that")
    call bb.println("2 - we print 2 lines")
endfunction

function Do_4 takes nothing returns nothing
    call bb.clear()
endfunction

function Init takes nothing returns nothing
    call TriggerSleepAction(1)
    call Do_Init()
  
    call TriggerSleepAction(1)
    call Do_1()
  
    call TriggerSleepAction(2)
    call bb.clear()
    call TriggerSleepAction(0)
    call Do_2()
  
    call TriggerSleepAction(2)
    call bb.clear()
    call TriggerSleepAction(0)
    call Do_3()
endfunction

function init takes nothing returns nothing
    call ExecuteFunc("Init")
endfunction

endlibrary

(and in println hide/show is needed to refresh the board)

So finaly maybe have default created boards might be a good solution. (but else it would be not best way for me)

because it will only save a few MB of RAM
if it was senseful, the ram would not be important, but the thoughts behind it is more elegant to have it dynamic
 

Attachments

  • demo.w3x
    31.1 KB · Views: 77
Last edited:
Why not changing multiboards in println function over doing it in flush function? (since boards are created by default already, not too much actions are "overhead")

But as result the user has not to keep tracking of used strings (what he does need to do by him self now, in BBDebugMsg), and just can print line by line.

It seems more handy, and the "flush" maybe could really just clear the board.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Why not changing multiboards in println function over doing it in flush function? (since boards are created by default already, not too much actions are "overhead")

But as result the user has not to keep tracking of used strings (what he does need to do by him self now, in BBDebugMsg), and just can print line by line.

It seems more handy, and the "flush" maybe could really just clear the board.

Because the main use case is not the append/scroll like behaviour of BBJDebugMsg, but printing information about something when some event takes place.

e.g:
player selects a unit -> print unit stats/info
player hovers/clicks over an item (FSG) -> print item stats/info​

I.e for each event we completly overwrite the multiboard with different information and thats reflected in the api.
 
Alone the debug example would be much simpler?

You always can just use "println" with your message, instead of storing all strings and looping each time you wanna add a string.

On one side, one is forced or to store all strings/or printing all information at once || and on the other side, one can make it gradually, print step by step if needed.

this is optimized for ease of use.
from the suggestion it would not lose anything I think.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
You argue with ease use, but at same the api forces to keep track of all strings and doesn't allow simple adding or line removal.

Suppose we wanted to have a multiboard that printed the names of all players currently in the game.

We could do it this way (lets call it the "line removal" way):
JASS:
At the beginning (0 seconds after map loads) we create our multiboard, iterate all the players and add their names to it.
Then, when a player leaves, we set the row that corresponds to that player to blank (or we gray out the name, depending how we want to show to the outher players that that player has left the game).
When we want to show that multiboard we just call MultiboardDisplay.

Or we could do it like so (lets call it the "reprint" way):
JASS:
When we want to show the multiboard we iterate all the players and add (println) their names to it.
When a player leaves and we want to show the multiboard we iterate all the players, detect which players have left the game, and we add/print "" (or we gray out the name, etc.) if they have, otherwise we normally add/print the player name.

My point is that having the ability to remove lines is equavalent to reprinting the multiboard with those rows/lines left out/modified in some way.
In other words, YAGNI.

And also "keeping track of all strings" is rather easy in my opinion (2 variables, 1 array of strings, 1 integer counter).
 
And also "keeping track of all strings" is rather easy
You construct a scenario where it's okay like it is, but ignoring other cases.
And it's not about it's "easy" or too "hard" to keep track of strings, but it's a useless requirement.

I don't even have to think about an example myself, look at your debug example.. instead of adding a line into the debug queue, each time you need to loop and take care of your array. Just why ;O

Is there something positive that you can not clear multiboard with flush() and and not simply add a line without storing all strings except the "I don't want" argument?;s
 
Level 13
Joined
Nov 7, 2014
Messages
571
You want to first print a few lines:
JASS:
    call bb.println("A")
    call bb.println("B")
    call bb.flush(bb.TRSII_WIDTH).title("|cffFFFFFFBlackboard|r").show()

and then append another?:
JASS:
    call bb.println("C")
    call bb.flush(bb.TRSII_WIDTH).title("|cffFFFFFFBlackboard|r").show()
    // bb now prints:
    // A
    // B
    // C

I guess I could add a flag to the flush method that would skip/set this:
set this.ln_count = 0

which would make append "easier" but we still have to go through all the lines and move/copy them to the selected multiboard.

So in the end this change would only save you 38 printlns in the best case (appending a single line to 38 other lines).
And thats basically nothing:
JASS:
globals
    Blackboard bb // initialized elsewhere
    string array lines
    integer lines_count = 0
endglobals
function append_line takes string s returns nothing
    local integer i

    set lines_count = lines_count + 1
    set lines[lines_count] = s

    set i = 1
    loop
        exitwhen i > lines_count
        call bb.println(lines[i])
        set i = i + 1
    endloop
    call bb.flush(bb.TRSII_WIDTH).title("|cffFFFFFFBlackboard|r").show()
endfunction
Sure its longer than just having a flag to flush, but I don't want to change the API just to support this specific use case.

Note that BBDebugMsg doesn't simply append lines, it also emulates the scrolling effect (older lines move up; could also use a queue for the implementation).
 
I still don't get it.
The system makes it "easy" but the user needs to use own structure and logics to make things good working.

If you want the system fit for such needs like explained above for debugmsg, then make an append and remove function inside the system.
User can simply add() add() add(), and if it's full he calls a removeFront() or so and the show can go on.

Why he needs to track all string for all boards he create. He will need an extra struct for just managing boards logics.

But the basic problem for me is that I really can not just make...

println(1)
and after 2 seconds make
println(2)

... without any extra data structure.

So in the end this change would only save you 38 printlns in the best case (appending a single line to 38 other lines).
And thats basically nothing:
It is extra println for each function call, each time one would want to append something.
And... loop and storing is required.

why you so badly want to force printing ALL info at once? where is the benefit?
Even when you say you don't need something like removeElement or removeFront to be included, I see nooooo reason why I need to save my strings for simple append.
 
Yeh, sorry, I don't find it as ease use personaly with not being able to print lines independantly.
So, pre-creating boards is no problem, and actually totaly fine, but I can't befriend with API.
Other than the approach it works fine as Aniki wants, and code works.

I have no problem if someone else reviews it and would judge to approve it.
In case noone says something for next 2 weeks or so, it maybe will be graveyarded.
 
Top