Poll
Question: Would you like the wrapper to expose proper classes? If so, how should the default console and random stream be named?
No, C-style functions are fine. - 3 (10.7%)
Yes, with the default instances of "Console" and "Random" named "console" and "random" - 22 (78.6%)
Yes, but those names for the default instances are confusing, use something else - 3 (10.7%)
Total Voters: 27

Pages: [1] 2
  Print  
Author Topic: Python wrapper with classes  (Read 13858 times)
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« on: February 20, 2010, 06:23:55 pm »

As you probably noticed if you follow the forum regularly, there's been a recent discussion about providing classes in the python wrapper to expose the functions in a more OOP-like manner. This would mean a change from this code style:

Code:
import libtcodpy as libtcod

rand = libtcod.random_new()
x = libtcod.random_get_int(rand, 10, 20)

con = libtcod.console_new(80, 60)
libtcod.console_blit(con, ...)

To this:

Code:
import libtcodpy as libtcod

rand = libtcod.Random()
x = rand.get_int(10, 20)

con = libtcod.Console(80, 60)
con.blit(...)

At first, the code would be duplicated, both in C-style and OOP-style, so your code wouldn't stop working just like that, and you can take time to make the transition. However, we couldn't maintain both versions forever, and probably in the next major libtcod version (which should take a while!) we would finally remove the old code (with plenty of **compatibility break** warnings; an automatic tool to find deprecated code could also be arranged Wink ).

Only functions that deal with an instance of some kind will be changed (ie, console, random, image...). Stuff like system functions will remain as they are. This is not a "NASA architecture" thing with multiple inheritance and all that; we'll just replace "class_method(instance, ...)" with "instance.method(...)".

This poll is to gauge interest in the python/libtcod users community. It may be a pain to change, but in the end your calls to libtcod should end up: (a) much shorter, (b) easier to look at (without tons of underscores Smiley ) and (c) more pythonic (whatever that means!). However, in the end this is for you guys, so your opinion matters a lot. Direct any questions to this thread!



There's also the matter of default instances. This only applies to the Console and Random classes. The root console and default random stream are currently referred to with the special code "0", but we can't have stuff like "0.blit(...)" -- we need default instances to refer to. They will be created automatically and be available at all times. I'm sure they'll be used a lot, so they need to have short names. To give you an idea, long names could be:

  • libtcod.root_console
  • libtcod.default_random or libtcod.random_stream

Relying on the convention that class names are LikeThis and instance names are like_this, a shorter version would be:

  • libtcod.console
  • libtcod.random

However, I understand this could be confused with the Console and Random classes (right?). Please vote and, if you agree with the change, but not this naming convention, suggest alternatives in this thread.



Ok, that's it. Important stuff! Sorry for the long post Smiley
« Last Edit: February 20, 2010, 06:28:23 pm by Jotaf » Logged
george
Master
****
Posts: 216


View Profile
« Reply #1 on: February 20, 2010, 06:44:24 pm »

Voted for the shorter names. I realized that when I write Python I do it that way all the time anyway (i.e., class Foo: .... ; foo = Foo()).

My only question about a rewrite is, how much of the current wrapper is handmade by jice and how much of it could be automated? The one thing I wouldn't want to see is a Python wrapper going well and then dying out as maintainers move on or whatever, and jice is stuck with an unmaintained wrapper.
Logged
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #2 on: February 20, 2010, 08:15:12 pm »

Well, that's one of the reasons why I don't want a complicated wrapper. It will mostly be a change in syntactic sugar, so it will still follow the C API closely and be just as easy to maintain as the current wrapper.

I don't know how flexible any sort of automation would be, but most functions are extremely easy to wrap as they are -- almost all are one-liners!

Of course, there are a lot of people interested in keeping the python wrapper healthy, so even in the off chance that me or Jice don't have much time for it, it should be easy for a trusted person to wrap new functions without being exactly a guru Smiley
« Last Edit: February 20, 2010, 08:20:01 pm by Jotaf » Logged
jeslimak
Defender
*****
Posts: 55


View Profile
« Reply #3 on: February 20, 2010, 09:44:06 pm »

One of the things that will probably need a bit of an overhaul will be the line module.  It just doesn't work as a class how it's handled at the moment.  Luckily, it's only a few functions that can be ported out of the C code relatively easily.
Logged
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #4 on: February 21, 2010, 03:08:19 am »

Hm, it's not that bad. It's simple and easy to use. You can't "draw" more than one line at the same time since it uses global state, but it doesn't seem like a huge priority. One python-specific function that could be handy could be one that returns a list of coordinate pairs to be iterated on, or a generator function; so you could use it easily in a for loop. I dunno about re-implementing the logic on the python side since then libtcod and the wrapper may start to diverge a lot, but if it's really necessary it can be done.
Logged
george
Master
****
Posts: 216


View Profile
« Reply #5 on: February 21, 2010, 07:22:43 am »

I think most (all?) are in agreement to keep the wrapper light. A set of Pythonic extensions as a third party add-on module would be neat though. I love stuff like generators and I think they'd work great for a roguelike.
Logged
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #6 on: February 21, 2010, 04:26:26 pm »

Generators are just an easier way of looping (from the user's point of view), so they probably belong in the wrapper. They'd only be needed in 3 modules anyway: line, bsp and path.
Logged
Altefcat
Moderator
Protector
*****
Posts: 43



View Profile WWW
« Reply #7 on: February 21, 2010, 08:24:11 pm »

I do think this is a really good idea to rewrite the wrapper. Why not do it in the pygame way? We could have libtcod.init_rootconsole() that returns a console that we affect to the root console (http://www.pygame.org/docs/ref/display.html#pygame.display.set_mode); and create other consoles the 'object' way.
Logged

Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #8 on: February 21, 2010, 09:58:31 pm »

Well, the basic idea is this: the root console is always accessible as libtcod.console; you create off-screen consoles with con=Console(width,height). So I think it's already the "pygame way" Smiley
Logged
george
Master
****
Posts: 216


View Profile
« Reply #9 on: February 21, 2010, 10:43:31 pm »

Indeed, that's exactly what I've been doing in my UI code already, so sounds good to me.
Logged
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #10 on: March 01, 2010, 10:12:34 pm »

Ok, since "yes" is winning, at this point it would be cool for anyone who voted for the other options to defend their case so we don't step on anyone's toes -- compromises are possible Smiley

---

To everyone else: Since the wrapper is thin, I've been toying with the idea of building the classes programatically from the defined functions (or the other way around -- convert to classes, and add the backward-compatible functions programatically). This seems within reach of python's introspection capabilities, and would avoid almost doubling the size of the wrapper.

Basically I'd create the new classes with no methods, and have a piece of code that would scan all functions in the file and add the appropriate ones (eg, starting with "console_") as methods to those classes. The first argument is already the class instance, which works nicely with python's convention on methods (first argument is "self"). This would save hundreds of lines of code and prevent code duplication.

What do you think? Too crazy?
« Last Edit: March 01, 2010, 10:17:40 pm by Jotaf » Logged
Altefcat
Moderator
Protector
*****
Posts: 43



View Profile WWW
« Reply #11 on: March 02, 2010, 12:22:02 am »

That seems just enough crazy to have a chance to succeed, and I'd be very interested to see your idea become real!
Logged

george
Master
****
Posts: 216


View Profile
« Reply #12 on: March 02, 2010, 12:28:45 am »

Yes, this is a brilliant idea.
Logged
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #13 on: March 08, 2010, 12:24:38 am »

Ok then, I'll share some of the details with y'all Cheesy

I just put the existing functions inside the Console class; then instead of using the first argument as the console identifier, they use the identifier stored inside the class.

The interesting part is this:
Code:
_dummy_console = Console(None, None, None)

class _ConsoleMethod:
    def __init__(self, method):
        self.method = method
    
    def __call__(self, con, *args, **kwargs):
        _dummy_console.con = con
        return self.method(_dummy_console, *args, **kwargs)

for (name, method) in vars(Console).iteritems():
    if name[0] != '_' and callable(method):  # public method
        globals()['console_' + name] = _ConsoleMethod(method)

_ConsoleMethod is a class that looks like a function, so it can be "called". It sets up the console identifier in a dummy console class instance, and calls the specified method. Then we just go through every public method of Console and create the _ConsoleMethod instance, that will act like a function by calling the corresponding method. So the blit method becomes the console_blit function. Smiley

I warned you it was crazy Grin

Anyway the upside is that there's no duplicated code, and the sample still works so it's fully compatible!

The Console, Random and Image classes are in. Any others that should be... "classified"? Cheesy
« Last Edit: March 08, 2010, 01:58:57 am by Jotaf » Logged
Jotaf
Global Moderator
Master
*****
Posts: 1183


View Profile
« Reply #14 on: March 08, 2010, 04:19:03 am »

I've attached an updated wrapper with Console, Random and Image classes. It should be fully compatible with the earlier version (I tested it on the sample). Also, I modified the python roguelike tutorial to make use of the new syntax.

Some highlights:

Code:
libtcod.console_put_char(0, self.x, self.y, ' ', libtcod.BKGND_NONE)
becomes
Code:
console.put_char(self.x, self.y, ' ', libtcod.BKGND_NONE)

And

Code:
if libtcod.random_get_int(0, 0, 1) == 1:
becomes
Code:
if random.get_int(0, 1) == 1:

For the truly adventurous among you to experiment with and give feedback, and help shape the next version of libtcod Cheesy

* libtcodpy.py (52.06 KB - downloaded 422 times.)
* libtcod_tutorial2_5_modified.py (8.23 KB - downloaded 413 times.)
Logged
Pages: [1] 2
  Print  
 
Jump to: