14. Juni 2007
Früher...
Google ist dein Freund - tatsächlich, das Lied war im Original von "House of Love", einer Band, die damals vor langer Zeit (ca. 1988) als eine von den Manchester-Bands bezeichnet wurde.
Ach war das schön damals... Ich hab sie noch im Dortmunder Live Station gesehen - ein Konzert in fast familiärer Athmosphäre war das.
Und YouTube ist auch dein Freund - dort findet man tatsächlich ein Video von House of Love - Shine On. Sehr schön auch "Man To Child".
Und in der Bücherei hab ich nochmal zwei alte Romane von der Scheibenwelt ausgegraben, die aus heutiger Sicht noch irgendwie unfertig wirken:
- "Das Licht der Phantasie" mit einem frühen Auftritt von Rincewind (die Hauptfigur, mit der ich am wenigsten anfangen kann) und Zweiblum.
- und "Das Erbe des Zauberers", das im Original mit "Equal Rites" viel witziger tituliert ist, mit einer Oma Wetterwachs, die ohne Nanny Ogg nur halb so gut ist.
Aber ich konnte mich halt an die Bücher nur noch sehr schwach erinnern...
Programmieren mit Panda
Im Prinzip war es ein Refactoring.
Zunächst wollte im zweiten Level die Steuerung nicht funktionieren, und es waren irgendwelche Phantom-Objekte vorhanden.
Beides lag daran, dass ich die Objekte im Baum "render" und die Tasks im globalen TaskManager "taskMgr" beim Ende des ersten Levels bzw. beim Start des nächsten Levels nicht richtig gelöscht bzw. neu angelegt hatte.
Die einfachen Befehl
render.ls()
print taskMgr
haben aber sehr dabei geholfen, dass rauszufinden - obwohl mir immer noch nicht klar ist, warum Tasks, die ich soeben entfernt habe, von print taskMgr immer noch aufgelistet werden.
Weil Python so viel Spass macht und ich das händische Kopieren leid war, habe ich mir auch noch gleich ein kleines Backupskript backup.py angelegt:
#!/bin/env python
# -*- coding: iso-8859-1 -*-
import os
import os.path
import glob
import time
import shutil
dateStr = time.strftime("%Y%m%d-%H%M%S")
thisDir = "."
backupDir = os.path.join (thisDir, "backup", dateStr)
try: os.mkdir(backupDir)
dateien = os.listdir(thisDir)
numFiles = 0
for datei in dateien:
fullname = os.path.join (thisDir, datei)
if os.path.isfile(fullname):
base, ext = os.path.splitext(datei)
if ext not in [".log", ".py", ".obj"]:
print "Datei gesichert:", datei
shutil.copy2 (os.path.join (thisDir, datei), backupDir)
numFiles += 1
else:
print "Unterverzeichnis ignoriert:", datei
_ = raw_input("Backup erledigt (%d Dateien gesichert)" % numFiles)
except:
import traceback
traceback.print_exc()
_ = raw_input ("Backup konnte nicht komplett durchgeführt werden!")
12. Juni 2007
Spieleprogrammierung mit Panda3D
Als erste Übung will ich jetzt mal eine Art 3D-PacMan bauen, um mal in das Ganze so reinzukommen.
Aller Anfang ist schwer, aber jetzt habe ich den Punkt erreicht, wo ein Programm grundsätzlich funktioniert und es scheinbar immer einfacher wird, neue Features einzubauen.
Immerhin findet mein Sohn (7) das Spiel schon so schön, dass er ständig quengelt weil er spielen möchte.
Am meisten Zeit ist bisher für die Collision Detection draufgegangen, weil die zwar grundsätzlich funktioniert, dann aber scheinbar etwas falsche Werte zurückliefert, so dass z.B. der Avatar leicht in die Wände hinein rennt und dann zu weit zurückprallt und sich die Gegner ständig in den Ecken "verheddert" haben.
Für die Gegner habe ich es inzwischen durch gute alte handgestrickte einfachste Geometrie-Routinen in den Griff gekriegt, und beim Avatar stört es nicht ganz so sehr.
Über den weiteren Fortschritt werde ich hier gelegentlich berichten.
Multi-Threading ist böse - Multi-Threading is evil
Bei Python gibt es da zwei spezielle Probleme:
- Im Gegensatz zu Java lassen sich Threads nicht im Notfall "abschießen".
- Wenn der Haupt-Thread endet, läuft das Programm trotzdem weiter -quasi scheintot - es sei denn, man arbeitet für alle Worker-Threads explizit mit setDaemon(true). Der Prozess wird dann beendet, sobald nur noch Daemon-Threads übrig sind.
Neben diesen Python-spezifischen Problemen gab es am Anfang die üblichen Schwierigkeiten mit
- Race Conditions bei konkurrienden Zugriffen, die zu falschen Ergebnissen führen,
- so dass schließlich an allen Ecken und Enden ein explizites Locking erforderlich war.
- Durch ungeschicktes Locking (unterschiedliche Reihenfolge) kam es dann gelegentlich zu Deadlocks, die schwierig zu finden waren (letzten Endes konnte ich dann aber programmatisch zur Laufzeit Warnungen bei möglichen Deadlocks ausgeben).
Viel gemeiner und bei weitem schwieriger zu entdecken waren aber die Probleme, die durch Race Conditions auftreten können, wenn ein Thread gerade eine Datei schreibt und ein anderer Thread in dieser Zeit einen Unterprozess startet, z.B. mit dem subprocess-Modul. Der Unterprozess erbt dann das Datei-Handles (unter Windows), woran natürlich niemand denkt. Dadurch ist die Datei für Windows quasi so lange zum Schreiben geöffnet, wie der Unterprozess läuft - z.B. kann der Prozess, der die Datei eigentlich geschrieben hat, die Datei anschließend nicht mit os.rename umbenennen. (Ja ja, ans zwischenzeitliche close habe ich gedacht).
Die Ursache ist die, dass alle Handles an Unterprozesse vererbt werden, wenn es nicht für das Handle explizit ausgeschaltet wird. Jedes Handle mit dem normalen open(...) wird also vererbt.
Als Workaround muss dann anstelle von outf = open (fname, "w") der Aufruf outf = os.fdopen (os.open (fname, os.O_WRITE + os.O_NOINHERIT + ....), "w") verwendet werden.