Share your Panda development experiences!

This is a thread for all of us to share our development experiences with Panda; tips, tricks, and maybe a short story or two. I guess I’ll go first :slight_smile:

Using DirectStart

One thing I notice often in the examples in the Panda manual is the use of direct.directbase.DirectStart. While convenient for small examples, like those in the manual, I recommend staying away from it except for use in one-file, proof-of-concept scripts, for quite a few reasons:

  • Code completion in several IDEs mess up when it’s used; often starting up a Panda window
  • It breaks the “explicit is better than implicit” rule in Python: It runs code when it is imported - not normal, and implicit
  • It is hard to understand for someone not familiar with Panda; when you’re introducing new developers to your project and showing them around your codebase, this can be a problem.

All of the problems associated with direct.directbase.DirectStart can be avoided by use of ShowBase, like so:

# Instead of this:
import direct.directbase.DirectStart

# Use this:
import direct.showbase
window = direct.showbase.ShowBase()

# The ShowBase instance replaces global "base"
# window.render replaces global "render"
# window.cam replaces global "cam"
# window.render2d replaces global "render2d"
# window.loader replaces global "loader"
# And so on...

Next, pass your instance of ShowBase around as a parameter wherever it’s needed, completely avoiding the problems associated with usage of direct.directbase.DirectStart.

I know, I know, most (if not all) experienced Panda developers already know this trick, but I see “Is there any alternative to direct.directbase.DirectStart?” asked over and over on the forums and the #panda3d IRC channel, so I thought I’d add this as one of my tips.

Unit testing your game

Unit tests are a good way to automatically make sure your code is working as intended. However, unit testing code that opens and closes a Panda window can get pretty arduous. If you have hundreds of such tests - which is not uncommon - unit testing can become quite a chore. Here are my two solutions to that problem:

  • Design your game’s architecture in such a way so that business logic (i.e, things like attack damage, enemy behavior, etc…) is completely independent from the representation of said logic (like attack special effects, so your game’s logic can be tested without Panda. This is good programming practice in general, too.
  • Request that Panda open up no window by adding the following line to your Config.prc:
window-type none

Alternatively, you can use:

ConfigVariableString("window-type","none").setValue("none")

This solution is very beneficial for unit testing code that must use Panda - your special effects code, for example. You could possibly run unit tests on a dedicated build server with this solution, since no rendering device is needed.

Those are my tips - I can’t wait to see everyone else’s :slight_smile:

Just one tip: test a feature before you’ll start thinking of using it.

The first thing that broke my fist project was the pipline. I wanted to use 3ds max and models made by third parties, but they were rigged with ‘helpers’ not bones and the export script only understands bones. I ended up without animations.

The next pipline problem was multiple uv that again are not supported by the max export. My terrain is now mapped with a texture atlas (but I’ve got normal, glos and glow mapping for it with the autoshader only).

PandaAI. It just was too good to be true. All worked fine, until I noticed that npc cheat on hi-end machines. The AI is not framerate independent, and is almost usless for action games. Realtime combat had to remade into gird based, turn based.

Fog + shadows. It’s a killer for my target hardware. I can only have one or the other. I had to change the camera so that the far plane is never in view so I don’t need to use fog to hide the empty scenery.

Placing DirectGui elements. Puting a button in a corrner usualy makes 3/4 of the button out of the screen. Now every element I make has its own geom with the pivot set so that it will stay on screen (but only when parented to the rigth a2d* node).

These are all problems I could avoid if only I tested stuff, not just only read about them and think they work in all situations.

I think the listed disadvantages (if we’ll assume we agree on all of them) of DirectStart are not enough to conclude that it is not suited for large scale projects.
Actually, if you are going to import ShowBase and create a single instance, that’s what DirectStart.py mainly does anyway.

print 'DirectStart: Starting the game.'

from direct.showbase import ShowBase
base = ShowBase.ShowBase()

If there is more to it, let me know.

Did I mention the implicit global DirectStart makes? :stuck_out_tongue:

I disagree. The abnormal results of importing DirectStart is enough to stay away from it alone. Add that to the fact you’re forced to use implicit global variables throughout your code (even the single ShowBase instance DirectStart makes falls in this category), and I wouldn’t touch it with a 3.1415926535897931
-mile long pole :slight_smile:

Here’s why I think ShowBase is a better alternative: It’s clearer - both for those who have used Panda, but especially for those who haven’t used Panda. Plus, it’s easier to refactor too. So, why not use ShowBase?

I think the whole “direct” layer is pretty bad from code quality aspect. It has many depedencies, even circular ones and uses a lot of globals. Using global objects is okay if you keep it consistent and are aware of the consequences - I think none apply. Sometimes it’s really better using globals than passing an object around.
On Panda’s C++ side, you can see many good coding patterns applied. But on the Python side, I’m speaking about the “direct” package, there’s only mess. Functions glued together by force, modules that overuse the “global” statement, useless reimplementations of tools from the C++ layer (e.g. Messenger), implicit execution of application critical functions (e.g. DirectStart) and so on.
Sometimes it feels like a playground of unexperienced, but ambitious programmers. There are many convenience functions and classes in style of those who everybody writes for oneself, most of them undocumented (not even in code).

I’d wish there was a document about Panda’s philosophy and all developers would stick to that. We neither have a list of core classes or a list of classes users are supposed to get familiar with. You learn mostly by trying out things or reading others’ code.

Code quality, and software quality implied, comes from solid theory, a straightforward philosophy, which Panda3d sadly lacks.

Here’s an example of OGRE3D’s system: ogre3d.org/docs/manual/manual_4.html#SEC4 . That’s the second chapter of their manual, right after the intriduction. They use the singleton pattern a lot and they made that to their principle. Singletons for core functionality and Plugins for everything else. Seems to work out pretty well.

Unity doesn’t have that technical documentation, as it tries to emphasize its WYSIWYG and drag’n’drop way. Still they have a very clever system behind the curtain: unity3d.com/support/documentatio … nents.html .

Crystal Space doesn’t go very deep into philosophy either, but it does have a rough design document that describes how the engine is meant to work.

I didn’t look up all game engines, but I noticed that the more user friendly (graphical and responsive) engines encourage the ‘right’ usage through a well thought out GUI. Since Panda3d is only a library, we should focus on the theory and give the users a “designated way” that leads to good code - code that is easily extensible, testable, debuggable and simply straight forward.
Freedom is not always the best way. People want not only tools, but also instructions for those.

Long story short:
I think using Panda would be a lot less pain if there were a few clear design principles and a few diagrams showing those. A sample of a well thought out code design using Panda would be great either. And most important: clean up the code base! There’s so much undocumented and useless stuff in there.

Maybe I don’t understand what implicit global of DirectStart you are talking about. If you mean the global vars (around 20) it makes, that’s in the ShowBase class anyway, is that what you mean?

Anyway, stricktly personally I’ve never had problem using DirectStart even in large projects with 20+ modules and tens of thousands lines of code. Even if that’s not what God want’s me to do :stuck_out_tongue:

I meant, DirectStart forces you to use the globals defined by DirectStart; ShowBase, on the other hand, merely allows you to use the globals it defines. Although it’s true ShowBase defines them, you don’t have to use them - using them would almost be as bad as using DirectStart plus the globals ShowBase defines.

The general consensus in the software construction community is that unbridled use of globals - like DirectStart encourages - complicates your code, makes it harder for others to read, and is harder to refactor in general. Globals hidden behind access functions are better, but it’s far and away better to bite the bullet and refactor the globals into a Singleton or Borg/Monostate.

Please, try coding something small without unbridled use of globals (Singletons or similar are OK), and see how much easier coding becomes. At the very least, hide your global variables behind access functions, or encapsulate them in Singletons or Borgs. You code will sincerely thank you :slight_smile:

P.S: One other thing I’ve learned is “try not to point to how big your coding project is as an indication of the validity of your argument” - my Panda project spans 20k lines of code and 535 files - 125 of which are Python modules :stuck_out_tongue:

I don’t get it. What’s so evil about globals? Why window.render is good and render is bad?

Forces? How? If you want to do this:

class myGame(object):
    def __init__(self):
        self.pandaBase = base

And just pass around a pointer to “myGame” in order to refer to render like this:

self.myGame.pandaBase.render

Then you may well do it. Obviously, it’s horribly inconvenient, but you can do it.

Panda will still use its globals, obviously, because that’s how it’s made and you can’t do anything about it.

If you were using globals in your own code, that would quickly lead to confusion. But those made by Panda are well known and they’re there for the sake of convenience. These are the elements that you just need to have anyway, if you want to have a Panda application and because they’re called so freaking often in your code, it’s more convenient to keep them as globals, because it’s short and to the point.

I agree that this is something that could change in the future versions of Panda, and it’s been discussed numerous times in the past. I also understand your disposition, DangerOnTheRanger, because I know your code isn’t Panda-centric. Mine, however, is and I don’t think these well-known, well-documented globals are a reason to start building bunkers and collect food and water for the upcoming apocalypse. They’re there? Fine. They’re removed and replaced by something more “good-practices-aligned” in the future? Also fine.

I still don’t get it. All the DirectStart module does is create a ShowBase instance like you do here in your own module, right?

I’m not sure about this. But I agree this is the usual response. Though every time I ask “why?” I get “well that’s what the Bible says” reply. Maybe you’ll be the one to finally explain it to me.
Of course it’s hard to guess where the global is created and name clashes can happen, but there are only around 20 globals I could find in that class. Either way, you can always use “base.render”, instead of “render” in your code.

I didn’t say I use globals in my projects (I prefer Sigletons btw). But instantiating ShowBase in your code, or having it instantiated in DirectStart, what’s the difference? As far as I can tell builtins are defined inside that class anyway.

I’m not sure if you got the point. My argument was I am using DirectStart on a large project and don’t have any of the problems you mentioned I should. It shouldn’t matter if your project is larger, I’m just saying I do have a large project and don’t have those problems.

Correct. However, to those who have no prior experience with Panda, DirectStart is just another module, with no side-effects whatsoever - they don’t know it creates a ShowBase instance. Thus, by using DirectStart, you’re needlessly making your code hard to understand (“where does this ‘base’ variable come from?”)

Let me show you the problems with global variables:

Say you want to add a new function that performs some operation on a global variable. You add the function, and test your code. It dies with an exception - somewhere else in your code, you modified the variable in a way that is incompatible with your new function. How will you find what part of your code modified the global? Since you didn’t encapsulate your global inside a function/Singleton/Borg, you have no idea what part of code modifies the global in a way that causes your new code to halt and throw an exception.

Another example: Say you have a “plain” global (one not hidden behind a function or Singleton) in a multithreaded application, and you observe the global getting corrupted on occasion. Since you modify the global in several threads, how are you going to figure out which thread (if it is any single thread) is causing the problem? The best solution is to make the global thread-safe. However, since you access the global hundreds - if not thousands - of times throughout your program, refactoring can be a real chore.

Using base.render is just as confusing as render alone - people studying your code will have absolutely no idea where that variable comes from.

First off, DirectStart breaks a core Python rule - “Explicit is better than implicit”. When you make your own ShowBase instance, you are explicitly telling your readers that you are creating a window. On the other hand, when you import DirectStart, someone unfamiliar with Panda will think that it’s merely an unused import (since you’re implicitly creating a ShowBase instance). Thus, they might remove it.

Like I said before, using the globals ShowBase defines - especially considering Panda 1.8 will be thread-safe by default - is almost as bad as using DirectStart.

My point was/is: Just because you have a large project that works (from an end-users perspective), doesn’t mean your coding practices are good.

In conclusion. Globals do have their places, especially as manager-style objects, but used without any means of access protection, not only can they be hard to understand by someone unfamiliar with your code, they can be hard to refactor as well.

Sorry, but I’ll disagree here as well. A good programmer should know what he is importing. And he should know the API before using it. The documentation of Panda3d explains that DirectStart creates an instance of ShowBase.
And either way, I still don’t see how ignoring the 3 line DirectStart module and instansing Showbase yourself solves the whole issue with globals. If you didn’t say it does, then never mind.

I don’t quite undertsand this. I mean I understand the issue when you have many modules which can modify a variable which is available (global) for all of them, it becomes harder to find where the error was generated. But how will not having your global variables, say in a module names “Globals”, but in a singleton named “Globals” help?

same as the first quote reply.

Well that’s exactly what I mean by “the Bible says it so” argument. :slight_smile:

Sorry, “since I implicitly create a ShowBase instance”?

Well that really wasn’t what I was arguing. You said doing this this way makes it harder, well: it doesn’t for me. That’s all I meant. And ,sorry if I wasn’t clear, I mean’t DirectStart, not globals here. I use singletons usually in these kind of situations.

Do the maya and blender export plugins not suffer from these issues ?

Chrys

I never stated merely creating ShowBase yourself was any better than using DirectStart - I stated that using ShowBase and not using any of the globals it provides is the way to go. I agree a good programmer should know the API he is using to write code, but to merely understand code someone else has written, he should not have to study the manuals of every library said code uses. It is the responsibility of every programmer to make every single line of code they write understandable by those who don’t use the APIs they do.

What you suggested is little different (and barely better) than using “plain” globals. It would be better if the program itself would be refactored so that either the global variables are unnecessary, or that each global variable is encapsulated in its own Singleton, which is far and away better than using one big Singleton for everything.

Once you’ve refactored the program using one of the two above solutions, finding the offending code becomes easy. It would also be a simple matter to make the globals (if they were still necessary) thread-safe, too.

You don’t know why explicitly stating something is better than implicitly assuming it? Imagine if Panda’s developers thought likewise:

  • They’d never put the return values of any functions/methods in the manual - “you should know that already”, they’d say
  • Bugs would be ignored, since they’d assume that everyone already knows of them, and knows
  • There would be no assertions, since the developers would assume that given input was already clean

As you can see, Panda would be much worse if the developers made implicit assumptions.

Well tell me, what happens when you not use globals that it already becomes important if you are using ShowBase, not DirectStart?

I don’t think that’s possible. You can try but that’s it. At least from personal experience.
You could spam your code with comments which are obvious for a Panda3d programmer for the same reason, but I don’think it would be worth it.

“Every global in it’s own singleton”?

“Lying is bad. Therefore you should tell kids where babies come from and that Santa doesn’t exist”. What I mean is, you can’t say for all possible cases. The programmer might have many other questions, like “where is the window resolution, fullscreen, antialiasing, or default model paths set?”. In Panda, it’s set in a config file by default. You could explicitly set it in your code instead, but would that be better? I think we should consider both the pros and cons. One obvious thing it’s good is that you write less lines of code and that’s good to me, and I’ll have to agree with coppertop as well that having some Panda variables used in just about any Panda application (also) as globals is just convenient and short. :slight_smile:

I’m sure they export uv sets. The bones as helpers could be a problem. I don’t know a format that could export animations, rigging and IK from one package to another.

When I had this problem collada suport was in the works, blender 2.5 was just out and there was no chicken for 2.5. I didn’t want to start with 2.4 to export to egg when collada was to be the default format any day now…

I’ve rerigged some models with bones that the exporter supports and I’m happy with a texture atlas for my terrain (as I wrote I can use normal maps with it, with multitexturing I could not - not with the autoshader).

Easy.

  • You can easily see at a glance what parts of your code use Panda, and how - very useful for bug-tracking and refactoring
  • Since you have to pass your ShowBase instance around, you start to think “Does this function really need access to ShowBase”? Your code becomes cleaner as a result
  • Restricting access to ShowBase becomes easier too. For example, I bet quite a few people will want to cash in on Panda 1.8’s multi-threaded render pipeline, and add Python threads to their own project. However, race conditions can occur with unbridled access to ShowBase - something you can easily prevent if you restrict access to ShowBase

Restriction of scope is a very important thing to consider in software construction. This is why Python variables are not global by default - such a large scope is unnecessary and dangerous.

Let me show you a small example:

import direct.directbase.DirectStart

panda = loader.loadModel('panda')
panda.reparentTo(render)

run()

That can easily be cleaned up so someone who has never laid eyes on Panda (but who understands basic 3D graphics theory) can understand it pretty well:

import direct.showbase.ShowBase

window = direct.showbase.ShowBase.ShowBase()

panda = window.loader.loadModel('panda')
panda.reparentTo(window.render)

window.run()

Here, the reader knows exactly what variables are being used, where they are being used, and why they are being used - the names are pretty descriptive. The previous example - the one using DirectStart - made the snippet much harder to understand for someone new to Panda. Usage of a global in such a small snippet is nigh inconsequential, but replace that 5-line snippet with a 50k-line system, and you’ll start to see problems. I like the way Alex Martelli puts it:

The reasoning behind that statement is simple. The more clearly-defined your communication channels are in your program, the easier it will be to both find/fix bugs in your program and add new features. Less code will change as a result of adding new features and fixing bugs, too - also a good thing.

That’s the way it’s supposed to be, you know. Big OO rule (SRP, to be exact): Each class should only have one reason to change. If a class has only one reason to change, you get several advantages:

  • Bugs are easier to trace to that class, because no two classes in your system perform the same task
  • Adding features becomes easy - you know what code will need to change, and where
  • Refactoring becomes easier for the same reason adding features becomes easy - you know where to go to make a modification, and that modification does not spill into surrounding classes (the way refactoring should be)

For example, if I had a global PluginManager variable, a global AssetManager variable, and a global NetworkManager variable, I would make each variable a separate Singleton, as per both the Singleton design pattern and SRP.

In this specific case, the Panda config file is meant for default settings, not ones set by your program. So, you’ll probably load all those configuration variables from your own configuration file (like I do), instead of reading/writing to Config.prc. Plus, you can add a descriptive comment telling readers that anything that is not set by your custom configuration system gets retrieved from Panda’s global Config.prc file.

I’m not sure if I see how those points show your own instance of ShowBase allows more than an instance of ShowBase imported from another module.

However, global (builtin) functions are available such as print().
Globals are dangerous, but if around 20 globals are defined in the library you use which save you from typing more in so many places, I’m not sure if they are more bad than good.
To me “render” is as commonly used in Panda as “print()” in Python.
of course if you had builtin.print() instead of print(), you would for example be able to have your own function print() without overwriting, but are you ready to do “builtin.print()” each time?
Why didn’t the Python developers require you to import some module to be able to use those functions?

Well merely understanding the code is one thing and fixing/adding new features is another. Now someone who does that should go learn the library.

I don’t know what you’re answering. I just didn’t get what you meant, but seems you’re already proving that it’s right.

So? Importing DirectStart creates a default window, scene and camera, the same way ‘importing PandaModules’ loads Config.prc and creates default properties.

Reading your posts, DangerOnTheRanger, brings a couple of thoughts to my mind.

  1. You’ve read a lot of smart books, have a lot of academic knowledge and I respect that. No sarcasm here, I really do.

  2. None of your arguments convinces me that globals are bad in this particular case. They’re bad in most other cases, but for stuff that’s present throughout my whole code… well, that’s what builtins are there for, no? Otherwise they wouldn’t be in Python at all, if it was impossible to find a suitable use-case for them. I like it that render acts like it was a part of the language I code with, the redpanda’s analogy to print is spot on.

  3. I find your argument about people unfamiliar with Panda reading code referring to Panda ridiculous. Especially since you’ve said that they should have the basics of 3D graphics programming. Why? To me, that’s a bit hypocritical. I would bring your statements to an extreme, for the sake of my point, by saying that you should write your code in such way that anyone, familiar with 3D engines or not, should understand it. Maybe we should even write code that people unfamiliar with Python should understand. Sorry, but this just doesn’t get to me.

Also, I see your examples of reparenting a Panda the exact opposite way to you – I consider the second one too long and too complicated, because for someone familiar with Panda, there’s a lot of redundant information there. Information I got imprinted in my brain the first time I, yes, read the manual.

In fact, I’m actually surprised that you’re using Python in the first place, because this language is inherently implicit. That’s what dynamic typing is, instead of the programmer stating explicitly what type the variable should be, the language is dealing with that behind the programmer’s back. Same with general memory management, it’s all implicit, the language does things for you so you don’t have to.

That’s why I use it. I use Python because it’s a smart and convenient language that doesn’t require me deal with stuff it can deal with by itself. And I like Panda because it does the same.

Hi guys,

I think this discussion is full of excellent points on both sides, lots of well-reasoned and well-stated arguments all around. I think now might be a good time to stop–I think all points have been made, and I worry that it’s now in danger of devolving into a religious argument.

So, to recap, we’ve learned that there are good reasons to limit the use of globals, that there is some value in having a ShowBase that doesn’t use globals, but that globals are not necessarily evil in all cases or for all people.

David