Git Product home page Git Product logo

Comments (7)

HexDecimal avatar HexDecimal commented on May 29, 2024 1

I've been assuming that working directly with ch,bg,fg is equivalent to working with a distinct numpy array and then copying this array to ch/bg/fg.
Or rather, I've been assuming that it's better to directly work with ch,bg,fg, as it doesn't have the overhead of copying an entire numpy array to another.

But looking at the source I don't really understand how ch,fg,bg interact with console_c.
If I understand well, methods like print uses the libtcod API directly. Wouldn't it be better if instead of it they would manipulate ch,bg,fg instead, so that it does not leave the boundary of python until you want to blit the console ?

console_c is a struct TCOD_console* type. This struct is initialized once with pointers to the NumPy ch,fg,bg arrays which are created and owned by the Python Console itself. When console_c is used with a call to C only the pointer to the struct is passed and the C function can interact with the NumPy arrays as if they were managed by C the entire time. The overhead that remains comes from converting all the other Python parameters into a C format, which only adds up when you call the methods too frequently.

Re-implementing the methods in NumPy wouldn't solve the problem since the "Python -> C" step still occurs even when working with NumPy. You'd have to write "NumPy -> NumPy" code to skip that step. Also, only draw_rect and draw_frame can be vectorised, the printing functions would be much slower written in Python as I learned the hard way when I wrote python-tdl.

To be clear, I am aware that looping over a Numpy array is also not a good idea, but sometime, you need the loop anyway and I assume that looping over a Numpy array is better than looping over the console_c with a call to the libtcod API.

It's not always easy but if you can make sure your inputs are arrays then you can vectorise the entire operation once you're more familiar with NumPy.

from python-tcod.

HexDecimal avatar HexDecimal commented on May 29, 2024 1

So, it is safe to directly manipulate ch,bg,fg via Numpy method, without creating another Numpy array before-hand. In fact, it is better to manipulate ch,bg,fg directly especially if you don't use the same Numpy data type between your Numpy array and ch,bg,fg (because, then there would need to be some kind of cast above the array copy). (tell me if I'm wrong)

Casting from any NumPy array to another is often just as fast as copying regardless of type (it'd likely hit a memory speed bottleneck first, so smaller types are faster,) but casting large amounts of Python values to anything is slow. This discourages using Python classes for tiles and such, you'll want to implement them as a NumPy dtype instead.

So anything that could not be vectorized, I should use the printing function, and for the rest, I should use Numpy. So basically, I should use the printing function for anything that write text.

Methods like draw_rect are still good if you don't need more functionality. You might prefer python-tcod's pathfinding and field-of-view algorithms over making your own implementation, and you can profile your code to figure out what you need to improve right at this moment.

from python-tcod.

HexDecimal avatar HexDecimal commented on May 29, 2024

All of your examples seem to be solvable using either bg=color or bg=None and they are similar to the kind of code that came up often when I was updating the Python samples. I don't think that adding bg=None is overly verbose.

console = tcod.console.Console(width, height)
console.clear(bg=color1)
console.print(x, y, value, bg=None)

I'm very skeptical about a default of fg=None. You're assuming that the consoles foreground will be a solid color which is rarely the case. If the default was None then some users might expect the colors to be white-on-black based on its initial observable behavior or based on the previous libtcod API and then be surprised when sometimes the foreground text is not written out as white when they mix colors with the default values.

The current reasoning for white-on-black is that it's consistent everywhere. With the new methods you can read them just by looking at the calls themselves:

# ...
# Previous code does not affect method parameter defaults in any way.
console.print(x, y, text)  # White-on-black.
console.print_box(x, y, width, height, text)  # White-on-black.
console.draw_rect(x, y, width, height, ch)  # White-on-black.
console.clear()  # White-on-black.

# Non-default colors can be traced back to named variables, instead of being hidden as a default.
console.print(x, y, text, color1, color2)  # Colors from named variables.
console.print(x, y, text, bg=color2)  # White-on-color2.
console.print(x, y, text, **style)  # Colors and settings overridden by a named dictionary.

# Not overriding colors is a special circumstance.
console.print(x, y, text, None, None)  # Use existing foreground and background color.
console.print(x, y, text, bg=None)  # White text on the existing background color.
console.print(x, y, text, color1, None)  # color1 text on the existing background color.

Most importantly, the newer methods avoid this exaggerated but very real example:

foo()
console.print_(x, y, text)
bar()
console.print_(x, y, text)

The natural use of setting default values as they're needed can cause a huge mess:

foo()  # Breaks if default values are changed before calling.
console.print_(x, y, text)  # Light-yellow-on-grey, obviously!  Also, center justify!
bar()  # Depends on default values set by foo, so don't ever change them in or after foo.
console.print_(x, y, text)  # Different colors and blend mode than the above call to print_!
# Changing any defaults at this point will break the next call to foo.
# All of the above code works correctly, but is fragile.

If you can imagine me fixing the above code to remove the default values then you'll understand why I'm strongly against continuing to support them in any way.

bg=None is super useful, and it's definitely going to be used more often than the default value of bg=(0, 0, 0), but bg=None is too special to make it the default. I don't want to look at a call to console.print(x, y, text) and wonder if it was intentional for it to not override the foreground or background colors.

The default color of white-on-black is not a suggestion to actually use or prefer those colors, it's just documentation for how those functions behave by default when those parameters are not provided under the assumption that you'll be reading others code or your own. It's a placeholder until you can decide on any color scheme other than white-on-black.

from python-tcod.

FractalWire avatar FractalWire commented on May 29, 2024

It's definitely a difference of appreciation here and I can for sure use None to solve my problem.

If the default was None then some users might expect the colors to be white-on-black based on its initial observable behavior or based on the previous libtcod API

It could be the other way around if they previously defined fg/bg for the part where they are printing stuff.
And you can take some liberty with those new methods given that they are new, and the old one deprecated.

I tend to see the console as a place where you will assemble stuff progressively.
I guess I see a console like some div element in an html page. Once I set its color and text color, I expect the child of this div to have the same color and text color, except if the child overrides it. And a div's child printing arbitrarily white-on-black would be weird.
In a lot of way comparing div and console doesn't make sense, but I hope you can get what I'm going with this.
If I'm writing something on top of something else in the console, I expect this new thing to respect the previously defined attribute, like I would expect a div's child to respect its parent attribute.

console.print(x, y, text, color1, color2)  # Colors from named variables.
console.print(x, y, text, bg=color2)  # White-on-color2.
console.print(x, y, text, **style)  # Colors and settings overridden by a named dictionary.

Those examples assume perfect knowledge over where you want to print, which might not always be the case. Let's take an example :

draw_terrain(console)
draw_monster(console)

Here, monster are drawn on top of the terrain, and you can assume that a terrain does not know where are the monster, and a monster does not know on what terrain it is on.
But a monster on a river will have a blue background while a monster on some grass will have a green background.
You can separate drawing logic here.

Granted you can just use None in your print for the draw_monster function, but in my opinion, it should be the default behaviour.

If you can imagine me fixing the above code to remove the default values then you'll understand why I'm strongly against continuing to support them in any way.

I totally agree, your examples of the old print_ are nasty. But the new print does not use the default attributes and it should not. The only place where the default_bg/fg should be use is with the clear function. The default_alignement should not exist, and I don't fully understand the blend attribute yet to say if it could be usefull or not as a default.
With the new print, yes, parts of the console might have modified bg/fg between functions (with a default to None), but it is restricted changes, and if you don't want those, you can always override them.

bg=None is super useful, and it's definitely going to be used more often than the default value of bg=(0, 0, 0), but bg=None is too special to make it the default. I don't want to look at a call to console.print(x, y, text) and wonder if it was intentional for it to not override the foreground or background colors.

In that case, go to the bottom of it and make bg and fg mandatory arguments, not optional. White on black is too arbitrary, even if well documented.

from python-tcod.

HexDecimal avatar HexDecimal commented on May 29, 2024

In a lot of way comparing div and console doesn't make sense, but I hope you can get what I'm going with this.
If I'm writing something on top of something else in the console, I expect this new thing to respect the previously defined attribute, like I would expect a div's child to respect its parent attribute.

This isn't weird, there are many examples of terminal applications which are divided up into multiple parts like an web page, but this does seem to explain the unusual way you've been assigning to the bg array rather than just using clear().

To me that kind of stuff is done at a high-level, where you can have a class that encapsulates the console and lets you treat it like HTML. At that point the consoles default parameters hardly matter since that class would be micromanaging everything. libtcod actually has a GUI API but it was only in C++ so I wasn't able to port it. It'd also be easy to make a new API in Python if someone wanted to.

I see consoles as a low-level object, if you took away everything but the ch,fg,bg arrays then it'd still be a console. Anything else is just a convenient way to interact with those arrays.

draw_terrain(console)
draw_monster(console)

draw_terrain is a bad example for the print and draw methods. A good implementation of this would be to store the terrain graphics in a NumPy array and transfer it directly into the console arrays. Anything looping over all console indexes and calling Python functions would run very slow.

I'd also implement draw_monster using NumPy as I rarely ever use libtcod's blend modes.

I assume you were trying to say that if you don't specify colors to a method then it should assume the existing colors are okay. Like setting the colors for a UI element first before printing on top of it.

I still think white-on-black is more well defined than None/None but I'll go ahead and change it anyway since None/None would run a little bit faster and that's a decent enough reason.

from python-tcod.

FractalWire avatar FractalWire commented on May 29, 2024

To me that kind of stuff is done at a high-level, where you can have a class that encapsulates the console and lets you treat it like HTML.

For me too, most of the stuff that could be html-like would be treated at a higher-level. But that is what you said :

I assume you were trying to say that if you don't specify colors to a method then it should assume the existing colors are okay. Like setting the colors for a UI element first before printing on top of it.

If I change the value of a button, I don't care about color, I care only about printing new value.

At that point the consoles default parameters hardly matter since that class would be micromanaging everything. libtcod actually has a GUI API but it was only in C++ so I wasn't able to port it. It'd also be easy to make a new API in Python if someone wanted to.

I'm actually making exactly that here. A tree-like organisation for UI element that manages mouse/keyboard focus. But it's still pretty alpha and there is some weird stuff sometime. I'll probably post it on r/roguelikdev once I feel it's more stable.
But the default parameters of the console still matter as I found it more convenient to not rewrite my base color everytime when I draw something on the console.

I see consoles as a low-level object, if you took away everything but the ch,fg,bg arrays then it'd still be a console. Anything else is just a convenient way to interact with those arrays.

Yes, I was using width and height of the console, until I realized it was not a great idea, and that width, height should be managed at a higher level.

draw_terrain is a bad example for the print and draw methods. A good implementation of this would be to store the terrain graphics in a NumPy array and transfer it directly into the console arrays. Anything looping over all console indexes and calling Python functions would run very slow.

I've been assuming that working directly with ch,bg,fg is equivalent to working with a distinct numpy array and then copying this array to ch/bg/fg.
Or rather, I've been assuming that it's better to directly work with ch,bg,fg, as it doesn't have the overhead of copying an entire numpy array to another.

But looking at the source I don't really understand how ch,fg,bg interact with console_c.
If I understand well, methods like print uses the libtcod API directly. Wouldn't it be better if instead of it they would manipulate ch,bg,fg instead, so that it does not leave the boundary of python until you want to blit the console ?

To be clear, I am aware that looping over a Numpy array is also not a good idea, but sometime, you need the loop anyway and I assume that looping over a Numpy array is better than looping over the console_c with a call to the libtcod API.

from python-tcod.

FractalWire avatar FractalWire commented on May 29, 2024

console_c is a struct TCOD_console* type. This struct is initialized once with pointers to the NumPy ch,fg,bg arrays which are created and owned by the Python Console itself. When console_c is used with a call to C only the pointer to the struct is passed and the C function can interact with the NumPy arrays as if they were managed by C the entire time. The overhead that remains comes from converting all the other Python parameters into a C format, which only adds up when you call the methods too frequently.

Okay. I think I get it...
Both C and Python manipulate object in memory that have the same data type, so they can modify those without caring what the other language do with it.
That is some high-flying programming stuff.
So, it is safe to directly manipulate ch,bg,fg via Numpy method, without creating another Numpy array before-hand. In fact, it is better to manipulate ch,bg,fg directly especially if you don't use the same Numpy data type between your Numpy array and ch,bg,fg (because, then there would need to be some kind of cast above the array copy). (tell me if I'm wrong)

Re-implementing the methods in NumPy wouldn't solve the problem since the "Python -> C" step still occurs even when working with NumPy. You'd have to write "NumPy -> NumPy" code to skip that step. Also, only draw_rect and draw_frame can be vectorised, the printing functions would be much slower written in Python as I learned the hard way when I wrote python-tdl.

So anything that could not be vectorized, I should use the printing function, and for the rest, I should use Numpy. So basically, I should use the printing function for anything that write text.

It's not always easy but if you can make sure your inputs are arrays then you can vectorise the entire operation once you're more familiar with NumPy.

I definitely don't have this mindset yet. Vectorisation is hard.

from python-tcod.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.