Array Index Out of Bounds error on PGraphics.text()

Hello; new here. :wave:

I have no idea what part of this is breaking. I have tried to reduce this as best as I can to a minimal example.

I have a class defined in a separate tab which is meant to represent something like a basic terminal or console output screen. This class has its own PGraphics instance to which all the characters in the terminal should be written when an update is needed. Then the terminal should be output with a call to consoleRoot.show() in my main tab.

The line that causes my array index error is module.py:26. If I comment out this line, save, and run, there is no error. I have already tried replacing the arguments to text() with constants rather than looking up a value in a 2-dimensional list.

Main tab:

import random as rng
from module import Console

consoleRoot = None

def setup():
    global consoleRoot
    size(800, 600)
    consoleRoot = Console(100,100)
    frameRate(999)

def draw():
    global consoleRoot
    consoleRoot.update()
    consoleRoot.show()
    consoleRoot.set(rng.randint(0,99), rng.randint(0,99), rng.choice('abcdefghijklmnopqrstuvwxyz'))

module.py:

tileSize = 16

class Console:
    def __init__(self, w, h):
        self.width = w
        self.height = h
        self.graphics = createGraphics(w * tileSize, h * tileSize)
        self.graphics.beginDraw()
        self.graphics.textAlign(CENTER, CENTER)
        self.graphics.textSize(12)
        self.graphics.background(0)
        self.graphics.endDraw()
        self.character = [[' ' for i in range(h)] for j in range(w)]
        self._update = []
    def set(self, x, y, character):
        self.character[x][y] = character
        self._update.append((x,y))
    def update(self):
        if len(self._update) == 0:
            return
        self.graphics.beginDraw()
        for i in self._update:
            x, y = i
            self.graphics.rect(x * tileSize, y * tileSize, tileSize - 1, tileSize - 1)
            self.graphics.fill(224)
            self.graphics.text(self.character[x][y], int(x * tileSize), int(y * tileSize), int(tileSize), int(tileSize))
            self.graphics.fill(0)
        self.graphics.endDraw()
        del self._update[:]
    def show(self):
        image(self.graphics, 0, 0)

EDIT: To be clear (because I removed a line somewhere and confused my line numbers), the line that causes the error is in module.py and reads:

self.graphics.text(self.character[x][y], int(x * tileSize), int(y * tileSize), int(tileSize), int(tileSize))
1 Like

This is a Jython’s major shortcoming when dealing w/ Java’s overloaded methods & constructors: :coffee:

You see, method text() got 9 overloaded signatures: :see_no_evil:

At self.graphics.text(self.character[x][y], int(x * tileSize), int(y * tileSize), int(tileSize), int(tileSize)), you’re attempting to use text()'s 7th sig w/ 5 parameters:
text(str, x1, y1, x2, y2).

But I’m highly suspicious Jython actually invokes text()'s 4th sig instead, which also got 5 parameters:
text(chars, start, stop, x, y).

chars char[]: the alphanumeric symbols to be displayed
start int: array index at which to start writing characters
stop int: array index at which to stop writing characters

So it seems Jython is passing your self.character[x][y] argument as if it were a Java char[] array instead of an actual String! :prayer_beads:

Unfortunately, Jython doesn’t have any native feature to allow us to forcibly pick a particular overloaded sig of a Java method when its own auto choice fails. :crying_cat_face:

For such cases, our last recourse would be to rely on the Java’s reflection API as I did in the forum post I’ve linked above. :unamused:

But for your particular case, there’s a much easier workaround: Use Java’s own String datatype: :bulb:
Docs.Oracle.com/en/java/javase/11/docs/api/java.base/java/lang/String.html

from java.lang import String

def set(self, x, y, character):
    self.character[x][y] = String(character)
    self._update.append((x,y))
2 Likes

Thanks for explaining! I knew this had to be one of those “internal” problems with Processing.py.

Since you’ve pointed out that there is a text(chars, start, stop, x ,y) function that expects a couple of ints, I’ve simply turned those two arguments into floats to convince Jython to use the other signature. Now it works fine! :slight_smile:

1 Like

Well-thought out workaround! If the 2nd and/or the 3rd passed arguments are floating values, the 7th signature kicks in, b/c the 4th sig demands int values for its index parameters: :ok_hand:

size(100, 100)
noLoop()

pg = createGraphics(width, height)
s = 'QuickFox'

with pg.beginDraw():
    pg.textSize(020)
    pg.fill(0xffFFFF00)
    pg.text(s, 10, 10.0, width>>1, height>>1)

background(0xff0000FF)
image(pg, 0, 0)

Another workaround is to go w/ the 3rd sig if you don’t need the box constraint: :flushed:
text(str, x, y)

Notice this overloaded mess doesn’t affect the “global canvas” PApplet::text(): :open_mouth:
Py.Processing.org/reference/text.html

Only when we’re using the PGraphics::text() returned from PApplet::createGraphics(): :roll_eyes:
Py.Processing.org/reference/createGraphics.html

1 Like

Rather than using 2 separated lists to refer to the same thing, why not those 3 values in 1 list? :bulb:

def show(self): image(self.pg, 0, 0)


def set(self, x, y, c): self.chars.append(x, y, c)


def update(self):
    if not len(self.chars): return

    s = tileSize
    t = s - 1

    with self.pg.beginDraw():
        for ch in self.chars:
            x, y, c = ch
            xs = x*s
            ys = y*s

            self.pg.fill(0)
            self.pg.rect(xs, ys, t, t)

            self.pg.fill(0340)
            self.pg.text(c, float(xs), ys, s, s)

You mean combine the characters list with the _update list? I’ve developed my sketch to the point now where I can “pan” or scroll through a console larger than my window (think like a traditional ASCII roguelike), so I’m not sure that would work out unless I add an entire screen’s worth of indices to that update list whenever the screen is panned.

Otherwise, that would definitely be the way to go. I’m also considering having other layers (hence consoleRoot, as I may have other consoles).