Nuclex ---------------------------------------------------------------------------- Links ---------------------------------------------------------------------------- Techniques for data driven design http://ai.eecs.umich.edu/soar/Classes/494/talks/Schumaker.pdf Explicit template instantiation template class NCXGAME_API SigC::Signal1; typedef SigC::Signal1 onDrawSceneSignal; Flexine - A flexible engine http://www.codercorner.com/flexineNotes.htm _STL::basic_string<*>=<_M_start> ---------------------------------------------------------------------------- Richtlinien ---------------------------------------------------------------------------- * Eine abgeleitetete Klasse sollte einen Namen der Form besitzen. Dieser zusammengesetzte Name sollten keine sinnlosen Wortfetzen ergeben, sondern immernoch eine lesbare Bezeichnung darstellen. Beispiel: Ableitung einer speziellen Streamklasse und erneute Ableitung von dieser Klasse zur erneuten Spezialisierung: Stream TransmissionStream : Stream SerialTransmissionStream : TransmissionStream * Die Architektur sollte dem Benutzer bereits während des Entwurfs mitteilen, was er zu welchem Zeitpunkt tun darf und was nicht. Wenn eine Dateiklasse z.B. die Methoden 'Open()' und 'Close()' besitzt, dann könnte der Benutzer andere Methoden aufrufen, während die Datei geschlossen ist. Der Programmierer muss nun Gedanken daran vergeuden, den Zustand der Klasse zu verwalten. Schlimmer noch: Wird vergessen, die Datei zu öffnen, so taucht der Fehler erst zur Laufzeit auf - gemäss Murphy's Gesetz natürlich nicht vor dem Release der Komponente. * Die Modifizierer const und unsigned können dem Verwender einer Komponente nützliche Hinweise geben. Wenn es sich um eine Anzahl handelt, sollte z.B. ein unsigned-Wert benutzt werden, da es ausgeschlossen ist, dass die Anzahl negativ wird. Der Verwender braucht erst garnicht zu vermuten, dass es irgendwelche 'Spezialwerte' im negativen Bereich gibt. Auch const ist oftmals hilfreich: Erwartet eine Methode z.B. einen 'const int *', so kann der Aufrufer sicher sein, dass die Methode seine Daten nicht verändert. * Eine Funktion sollte entweder erfolgreich sein oder fehlschlagen, und nichts dazwischen. Wenn die Funktion beim auftreten eines Fehlers bereits Daten ausserhalb ihrer selbst manipuliert hat, so muss sie vor dem abgeben des Fehlers diese Änderungen zuerst rückgängig machen. Ist dies nicht möglich, so handelt es sich um einen fatalen Fehler, der beim abgeben zum Abbruch der kompletten Anwendung führen muss. * Eine der nützlichsten Techniken zur Vermeidung von Zustandsfehlern in Anwendungen ist es, dem Verwender schon durch die Architektur mitzuteilen, auf welche Funktionen/Daten er wann zugreifen darf. Ich beschreibe die Technik mal mit einem einfachen Beispiel: void openLogFile(const char *pszLogFilename); void closeLogFile(); void logString(const char *pszStringToLog); In diesem Fall ist natürlich noch leicht zu überschauen, dass die Funktion logString() erst nach openLogFile() aufgerufen werden darf. Wenn die Projekte grösser werden, kombiniert einem bisschen Schlampigkeit, so kann es unmöglich werden, die nötigen Vorbedingungen zu kontrollieren, die nötig sind, um eine Funktion auszuführen zu können. Die ganz einfache Lösung in der OOP ist eine Kontextklasse: class LogFile { public: LogFile(const char *pszLogFilename); ~LogFile(); void logString(const char *pszStringToLog); }; Es ist dem Verwender sofort klar, dass er auf die Methode logString() nur zugreifen kann, wenn die Klasse instanziiert (und damit die Logdatei geöffnet) wurde. Genau deshalb ist es eine schlechte Idee, die Klasse mit Methoden wie open() und close() zu erweitern. Diese Technik lässt sich auch viel allgemeiner einsetzen. Angenommen, um die Funktion drawPrimitive() aufzurufen müsste der Verwender zuerst beginScene() ausgeführt haben: class SceneContext { public: virtual void drawPrimitive() = 0; } class Renderer { SceneContext *beginScene(); }; So kann der Benutzer erst garnicht drawPrimitive() aufrufen, ohne vorher einen Renderer erstellt und mit diesem die Szene begonnen zu haben. * Informationen, die zusammengehören, nicht trennen Point2, Point3, Box2, Box3, etc. ---------------------------------------------------------------------------- Optimization ---------------------------------------------------------------------------- Rumors are that there are still people out there writing stuff like float fTemp = 1 / fDiv; fX *= fTemp; fY *= fTemp; or nValue &= 32; // modulo 32 nValue >>= 2; // div 4 instead of fX /= fDiv; fY /= fDiv; or nValue %= 32; nValue /= 4; Compilers went past this kind of optimization ages ago. ---------------------------------------------------------------------------- Nuclex server ---------------------------------------------------------------------------- ThingServer methods getThing(Key) Retrieve a thing by key addThing(Key, Thing) Add a thing to the manager registerThing(Key, Thing) ...if it is a factory-like stateless thing attachThing(Key, Thing) ...if the manager doesn't use the thing removeThing(Key, Thing) Removes a thing from the manager unregisterThing(Key) ...if it is a factory-like stateless thing detachThing(Key) ...if the manager doesn't use the thing clearThings() Removes all things from the manager enumThings() Returns an enumerator over all things currently managed The enumerator is a nested class usually named IThingEnumerator ---------------------------------------------------------------------------- Bitmap conventions ---------------------------------------------------------------------------- 0,0 - 0,0 = Grösse 0,0 0,0 - 1,1 = Grösse 1,1 Anders nicht in der Lage, Defaultparameter bei Image-Lock von 1x1 Pixel Bereich an 0,0 zu unterscheiden. Einzige Möglichkeit, Rectangle-Template ohne abs(X2 - X1) + 1 zu halten (abs schlecht, da Datentyp unbekannt, +1 katastrophal bei float, eg. 0.0f - 1.0f) Erspart Breitenberechnung ---------------------------------------------------------------------------- Library architecture ---------------------------------------------------------------------------- A common design is to just code most things you need for an application _________ in place. You don't have to use someone else's code and | | everything matches (more or less) together. | MyApp | |_________| This design is often used for small scale applications. However, it won't work so well if there's a large amount of code to be written, just to implement all features required for an application. Possibly, parts of this code will require knowledge not available to the developer(s). The logical step is to use third party libraries _________ ___________ to accomplish the given task. | | | | | MyApp |---| SomeLib | This will mix the conventions of the |_________| |___________| library with the application's, resulting in developers often writing wrappers to better fit the library into their code. If the wrappers are laid out well and sufficient abstraction is used, this will also enable the developer to replace the library by another one. Now, instead of tight-binding the application and library, you could create an abstracted interface for the functionality of the library, _________ ______________ put the wrapped library itself in a | : | dynamic library and let it register itself | MyApp : LibManager | to a library manager. |_________:______________| | | This way, you could, at runtime, get a ______|_ ____|___ list of the libraries available and choose | | | | the right one for each purpose. Eg. use | LibA | | LibB | LibA to load jpgs and pngs and use LibB, |________| |________| which doesn't support jpgs for tifs. This also allows the application to be extended with additional libraries without needing a recompile. ---------------------------------------------------------------------------- The dilemma of 3D engines ---------------------------------------------------------------------------- There are already too many 3D engines out there in the net. Every wanna-be game developer seems to have, at some time, the idea to write a better 3D engine that lives up to his standards which he deems ulimate. Most of them never cross the line of a big junk of unfinished code with hardly any seperation from the game they're written for. Even commercial products fail to archive generality under the excusation of performance reasons or limited development time. Out of the incapabilities of these enthusiasts results hard-to-understand nonstandard design, unsafe code, plain under-usage of common language features and, most of all, badly abstracted APIs, if there's anything provided which deserves to be called like that. Then, just about any of them reinvents the wheel in several regions at once. Instead of using common libraries, they start their own immature misconceptions of how things are supposed to work. And even if standard libraries are used, the entire codebase is then tightly coded just for a single library, requiring other developers to understand and rewrite the engine if they ever need to incorporate a feature not supported by that library. While operating systems advanced from monolithic blocks of tightly bound driver and application code to microkernel-based architectures, independent 3D engines seem to be getting nowhere at this time. ---------------------------------------------------------------------------- Bad programming practices at a high level ---------------------------------------------------------------------------- [1] Use Cut & Paste code duplication to avoid restructuring your existing code Not only will your project grow a lot faster, it will also generate new work at later times: -Increase the maintainance time by letting programmers that want to update a commonly used function search through the whole codebase -Prevent generality in a way that lets each function have different problems and bugs with the same data set -Of course, you'll get further increased compile times -No more short function calls describing what is happening, the entire function has to be carefully read and understood [2] Work around language features or don't use them at all By using as few language features as possible, you can reduce the learning curve required for new programmers joining the project, and also: -Have a chance to implement the features on your own, competing against the efficiency and flexibility of existing standards -Create your own bugs which you can then find and fix yourself instead of using other's prooven concepts -Make your project seem much more important with all the special handling involved to embed your custom features into the code -Make it possible to build your project on compilers that were written ten years ago and which somebody might just still have on his harddisk [3] Create headers that use symbols in other headers which they don't include. This excellent idea will make your project look far more complex to outside users, simply by: -Preventing the user from just quickly including a header he requires -If anything in the header's dependencies changes, all users of that header will have to be updated -Leading to an "include anything, just in case" philosophy greatly increasing compile times for longer coffee breaks -If applied correctly, creating wonderful complex include-order dependencies, forcing the programmer to puzzle each time he needs to use another header ---------------------------------------------------------------------------- SceneGraph ---------------------------------------------------------------------------- Jede Node hat total bounding volume aller Child nodes (so kann Octree in SceneGraph integriert werden). Node kann Attribute wie RenderStates, Material, etc. besitzen (zum gruppierten setzen) Attribute: TransformNode (enthält Trafo-Matrix) MaterialNode (setzt material, shader) StateNode (setzt render-stati) RenderNode (enthält Vertex-Buffer) Vorgeschriebene Reihenfolge des in-Effekt-tretens: Erst Transform dann State dann Material dann Rendern. SharedVertexBuffer irgendwo vorsehen. ---------------------------------------------------------------------------- Layers ---------------------------------------------------------------------------- +------------------------------------------------------------------+ | | | Management servers and related components (Scene/* etc.) | | | +------------------------------------------------------------------+ | | | Library servers and related components (Video/* Audio/* etc.) | | | +------------------------------------------------------------------+ | | | Kernel and auxiliary functions (Kernel/* Support/* Math/*) | | | +------------------------------------------------------------------+ ---------------------------------------------------------------------------- Glossary ---------------------------------------------------------------------------- Server A pure management class which gives access to a specific subsystem. In Nuclex, Servers are the connection points for plugins and give the user to access to a subsystem. The StorageServer, for example, manages the storage subsystem. Any integral Nuclex servers can be reached through the kernel. Widget A GUI element. This could be a button, a label or anything else typically found on graphical user interfaces. PlugIn A plugin which is dynamically loaded by nuclex. Plugins are normal dynamic libraries (like .dll on win32 or .so on linux) that export three functions. Nuclex plugins use the extension '.ncx' (release version) or '.ncd' (debug version). The nuclex kernel loads these plugins and lets them integrate themselfes into the engine. A plugin usually covers a specific area, like integrating a 3rd party library into the engine. Storage A storage is a container for streams. If you associate streams with files in a file system, a storage would be a folder. Nuclex streams can only be constructed by a storage Stream A data source/sink. Streams are an abstraction of files, you can read and write data from/to a stream, without actually knowing what the stream does with the data. For example, a stream could read data from a zip archive, the internet or a plain old file. Codec Decodes and/or encodes some file format. For example, a PNG codec would decode PNG files into linear memory bitmaps and vice versa. In Nuclex, codecs usually work with Streams rather than files, so the codec can also be used for embedded resources, compressed files or any other type of stream you can think of. Standard Input Input by keyboard and mouse as used for traditional windows systems ----------------------------------------------------------------------------- Working with a 3rd party library that is contained within nuclex ----------------------------------------------------------------------------- Nuclex makes use of a lot of 3rd party libraries through plugins. These 3rd party libraries can also be used by the application directly. For static libraries, it is simply a matter of linking the provided library to your application. If the 3rd party library uses a DLL however, you should follow these guidelines: The plugin module provided by nuclex for the specific library will already perform the library initialization, if neccessary. Please do not attempt to do this on your own, you will confuse the plugin. Instead, just link your application to the DLL's import library and to the nuclex plugin module's import library (eg. Lib/NCXAudiere/NCXAudierei[d].lib) as well. This makes the plugin module a requirement for running your application and gives you access the the plugin module's implementation classes. If there's something like a global library handle, there's usually an exported function in the plugin module's main header for retrieving it. If the 3rd party library is not designed for thread safety, you will also find an exported function which returns a ThreadGate, which you should use if you're working with multithreading. Only thing left to do is, include the 3rd party library's headers from the nuclex include folder and use it! ----------------------------------------------------------------------------- KeepAlive für Sounds ----------------------------------------------------------------------------- class Sound { public: void keepAlive(const shared_ptr &spSelf) { m_spSelf = spSelf; } void onPlayingFinished() { m_spSelf.reset(); // delete this } private: shared_ptr m_spSelf; ----------------------------------------------------------------------------- Ideen zur Lösung des downcast-dilemma: ----------------------------------------------------------------------------- - Texturen sind keine Interfaces. Wenn eine Textur erstellt wird, fordert sie beim TextureManager eine ID an. Der TextureManager informiert beim erstellen einer neuen Textur die Renderer über einen Event. Diese können nun in ihrer lokalen Verwaltung eine Textur mit gleicher ID erstellen, so dass beim Setzen der Textur nurnoch deren ID abgefragt wird. Evtl. ist das noch immer zu semantisch. Wenn der Renderer sich nicht anmeldet oder sonst irgendwie out-of-sync käme, könnten IDs bei einem Renderer funktionieren, beim anderen nicht, bzw. Übriggebliebene Texturen hätten eine falsche oder nicht existente ID. Der TextureManager dürfte keine clear()-Methode besitzen, sonst werden die Texturen ebenfalls nur semantisch invalidiert. Es enstehen ausserdem Probleme beim lock()/unlock(), da die Textur nun die Pixeldaten selbst halten müsste, und das Update nach unlock() an die Renderer weiterleiten müsste. Vielleicht garnicht so übel, man kann UpdateTexture() statt Lock() einsetzen. (Was ist schneller ?) - Textur-Interface besitzt Methode zum aktivieren der Textur. Die Implementationklasse der Textur bekommt einen Zeiger auf das Device und kann so typsicher die Textur aktivieren, ohne dass ein downcast nötig ist. Texturen können noch immer zwischen verschiedenen Renderern verwechselt werden. Jetzt ist das Problem vielleicht sogar schlimmer, denn die Textur könnte sich im falschen Renderer aktivieren! - Man kann einfach die letztere Methode nehmen, trotzdem noch eine selectTexture()-Methode beim Renderer veröffentlichen und hier mit einem dynamic_cast<> sicherstellen, dass die Textur dem Renderer gehört ----------------------------------------------------------------------------- SESE ----------------------------------------------------------------------------- http://www.cuj.com/experts/1912/alexandr.htm?topic=experts#3 [3] SESE is a mantra that soared during the days of structured programming. In the Orwellian SESE-land, every function must have only one entry (by design in most languages anyway) and one exit. The "single-exit" predicament is terribly obsolete today, in the era of exceptions. Although it's been shown time and again that SESE code is longer, less maintainable, more "McCabe complex," and less efficient than the equivalent multiple-exit code, there still are poor souls who are convinced that SESE is a great thing. (After all, Aristotle himself believed that the sun was scrolled onto the sky by flying angels.) SESE zealots were formed in the Pascal and C days. Unfortunately, if you do the age progression, those people are exactly today's development managers. ----------------------------------------------------------------------------- Ableitung ----------------------------------------------------------------------------- Es gibt zwei Arten von Ableitung: Klassifizierung und Abstraktion Klassifizierung = Volume Box Sphere Abstraktion = Stream FileStream MemoryStream ----------------------------------------------------------------------------- Artikel ----------------------------------------------------------------------------- Der unerkannte Feind eines jeden Programmierers sind automatische Entscheidungen, die man nie abgewogen hat. Man kann sie ganz leicht daran erkennen, dass man sich ihre Begründung erst ausdenken muss. Es ist nicht verkehrt, sich z.b. an wiederkehrende Entscheidungen zu gewöhnen und diese dann mehr intuitiv zu fällen, solange man sich um das genaue Einsatzgebiet und die genauen Auswirkungen beim antrainieren der Intuition im klaren war. Jedoch passiert es leicht, dass man Entscheidungen auf Basis von eigentlich niemals wirklich begründeten Gewohnheiten oder auch aus der Angst vor Abweichungen vom Bekannten fällt. Mancher fängt sogar an, eine höhere Ordnung in seiner Intuition zu erkennen, die ihm ja das Nachdenken über Entscheidungen komplett abnimmt, weil sie grundsätzlich sowieso richtig liegen muss. Wenn man ein unbekanntes englisches Wort liest, dann weiss man ja auch intuitiv, wie man es aussprechen muss. Aber liegt man nicht trotzdem hin und wieder daneben ? Wenn man sich die Aussprache von Grund auf nur nach der eigenen Intuition aufgebaut hätte, könnte man am Ende mit einem Engländer sprechen ? Anfangs wusste ich nicht, ob ich überladene Operatoren verwenden sollte oder nicht. Die einen sahen es als eine wesentliche Erleichterung, die anderen hielten es für eine Technik zur Codeverschleierung. Irgendwann kam für mich der Augenblick der Erleuchtung: Die Gegner von überladenen Operatoren sind ausnahmslos Idioten, die Angst vor dem Ungewohnten haben! Der einzige Kritikpunkt, mit dem sie argumentierten, war ihr eigenes Unverständnis und ihre Weigerung, sich mit neuem auseinanderzusetzen! Wie weit das gehen kann, zeigt das Beispiel eines Programmierers, dem ich vor längerer Zeit begegnet bin. Dieser war stolz darauf, sich ein Gefühl dafür erarbeitet zu haben, wieviele Rückgabewerte von Funktionen man im Schnitt nicht überprüfen sollte, damit ein Programm "gut läuft". Ein anderes Beispiel ist die Ignoranz mancher Programmierer, die behaupten, C++ zu schreiben, gegenüber namespaces. Die Notation sieht ungewohnt aus und man spürt ganz genau, dass man bekanntes Terrain verlässt, wenn man zum ersten mal einen namespace verwendet. Und dadurch glaubt man schnell, vor einem ganzen Berg unbekannter Verhaltensweisen des noch nicht durchchschauten Features zu stehen. Beim erlernen von neuem sollte man nicht Kompliziert mit Ungewohnt verwechseln. Viele halten die STL für kompliziert, nur weil die template-instanziierungen im Quellcode für sie fremdartig aussehen. Der erfahrene Programmierer sieht alles als ein Werkzeug mit bestimmten Eigenschaften. Er ist für jede Vorgehensweise offen, jedoch hat er schon Übung darin, zu sehen, über welche Vorgehensweisen es sich überhaupt lohnt, nachzudenken. Wenn er sich für eine Vorgehensweise entscheidet, dann deshalb, weil sie sich für das zu lösende Problem am besten eignet. Konsistenz gegenüber ähnlichen Lösungen ist ebenfalls von Relevanz, jedoch nicht Konsistenz gegenüber Altlasten, besonders wenn seitdem dazugelernt wurde. Wenn man die Meisterschaft erreicht, dann erkennt man den Weg zur Meisterschaft, der bei allen Professionen vergleichbar ist. ----------------------------------------------------------------------------- Features ----------------------------------------------------------------------------- State-of-the-art architecture - Clean and intuitive object model - Robust code and error handling - Very high overall consistency in design and code - Thoroughly documented using doxygen comments - Plugins can extend absolutely anything - Uses common solutions to common problems Features - Platform abstraction of the entire application - Cutting edge 3D shader-centric graphics engine - Fast hardware-accelerated 2D rendering - 3D positional audio and sound streaming - Extensive GUI with dynamic theme support - Scripting support for Python and Lua - Lightweight scene graph implementation File formats - Data can be read from files, zip archives and more - Decodes png, jpg, bmp, tif, tga and more - Sound streaming from wav, mod, mp3, ogg and more - Antialiased text output of ttf, fnt and more - Parsing of binary data, xml and more ----------------------------------------------------------------------------- Timeline ----------------------------------------------------------------------------- Support Plugin Image Video Audiere Input Scene Script |\ ___|_______|_______|_______|_______|_______|_______|_______|___| \ |###|#######|#######|#######|#######|#######|# | | \ |_______|_______|_______|_______|_______|_______|_______|_______ / | | | | | | | | / Math Storage Font Audio RStates GUI Terrain |/ ----------------------------------------------------------------------------- TODO ----------------------------------------------------------------------------- * Allow streaming of zipped files for mp3 playback * Throw out fmod and write an audiere plugin * Implement loki's SmallObjectAllocator * Relocate application window ownage to kernel * Remove vector-hack, operator-only, add conversion methods * Clean up matrix, operator-only, identity through const static member * Rename modules to plugins; plugin files to Xy.Plugin.dll, Xy-d.Plugin.dll * RenderState management * Include spirit in the stripped-down boost library * Rename Image to Bitmap. Image becomes a namespace * Rename Storage to Archive, so that Storage refers to the namespace * Use namespaces for all subsystems * Don't use derived string class any more * Replace __WIN32__ and __LINUX__ by NUCLEX_WIN32 and NUCLEX_LINUX * Create a GUI server based on windows and widgets * Input management system with event packets for GUI control * Refactor Variant, template VarType to() { return static_cast... } * Build an extensible Theme (class name probably 'DynamicTheme') * Introduce general unicode support to nuclex * Use ResourceSets for loading of serialized resource definitions * Invent 'style' attribute for GUI Check/Radio and HSlider/VSlider * Rename: VideoDevice -> VideoDriver; VideoRenderer -> VideoDevice * Relocate ImageServer and related classes to video namespace * Build a basic architecture for reading and displaying of static 3D models * Plugin class became a template and is not limited to nuclex kernel plugins anymore * Never going to need multiple kernels. Singletonizing it would make things far easier * Complete DirectInput plugin to support GUI and find a solution for text input * Decompose VertexDrawer into VertexCache (support 3D vertices!), TextureCache and Canvas * Some methods are still named addVideoDevice, removeAudioDevice instead of 'driver' * Implement hardware cursor support in video device and rendering context interfaces + TimeSpan doesn't handle overflows well. Correct that! + Write getBestVideoDriver(), getBestAudioDriver() methods + Primary video device should always be enumerated first on multimon systems + Extend model rendering system to support bone animated models (stat = mesh, dyn = model ?) + Write Kernel::initialize() instead of constructing in getInstance() + Allow custom window to be specified for Kernel::initialize() + Extend input system so that messages include modifier keys like Ctrl or Shift + Support CG and either CGFX or a self-developed effect file system + Return all system specific names using unicode ? Complete input system (text input and timeframe based state packets) ? All Enumerators should provide ++ and * instead of next() and get() and cycle() + Write RadioWidgetGroup to capture toggle events ? VertexBatches.back().RenderOperations.back().EndVertex cachen ? ? LockMode generalisieren (LM_READ, LM_READONLY -> gemeinsames Konzept) + Textauswahl in InputWidget einbauen + ListWidget, das als ComboBox geeignet ist (später mit SliderWidget) + InputWidget den Text scrollen und clippen lassen + Implement RenderTarget creation and usage in D3D9Renderer + XYWidget::Painter is a design fault. Remove it. + Input capturing is a requirement to make combo boxes work + Combo boxes would also need some popup-window-functionality + Modal windows are hard to realize or even impossible + Theme should be stored by each widget, also needed for input processing ? Video::VideoServer -> Video::Server ? Video::Manager ? Video::System ? ERRORS! + If an exception occurs while registering a plugin, some plugins might be unregistered without ever having their register function called + When an exception causes the application to quit, the kernel is destroyed but the plugins aren't unregistered. When unloadAllModules() gets called by the main() function or by the PluginServer's destructor, an access violation occurs because the plugins access the destroyed kernel + Thought the plugin class can now be used not only for nuclex plugins, there's no way to recognize whether a plugin is intended for the nuclex kernel or for some custom application thingy. Langzeit O Oxyd nachbauen O Marble Madness nachbauen O Bomberman nachbauen O XCOM nachbauen :) O Battlezone nachbauen O SNES-RPGs nachbauen O RPG Game-Engine ? Objekt (object) Akteur (actor) Entität (entity) Gegenstand (item) ---------------------------------------------------------------------------- RPG Objektmodell ---------------------------------------------------------------------------- Entity +Name +Description +Model // statical for display Actor +Weight +Inventory // also for boxes, etc. Character +Equipment +Slots[] Item +Worth Weapon +attack(Actor) Armor +defend(Attack) // returns weakened attack ? ---------------------------------------------------------------------------- RPG ---------------------------------------------------------------------------- Titel Bitter Hope Geschichte Kapiteltitel The Fire Still Burns Final Death (?) Two Millenia of constantly fighting Mankind faces (has met) its final decision ---------------------------------------------------------------------------- Bibliotheken ---------------------------------------------------------------------------- Nuclex hat zwei Arten, in welcher Fremdbibliotheken genutzt werden: Einverleibung und Unterstützung Bei der Einverleibung (z.B. Boost, SigC++, STL) werden die Fremdbibliotheken als erforderliche Komponenten angesehen und möglicherweise sogar in Teilen direkt in den Nuclex-namespace importiert (so geschehen bei Boost::shared_ptr oder std::string) Bei der Unterstützung stellt Nuclex ein generellen Interface für die Art der Fremdbibliothek zur Verfügung und die Fremdbibliothek wird von einem Plugin, welches quasi einen Adapter für das Nuclex-interface darstellt, als mögliche Implementation des Interfaces zur Verwendung durch Nuclex bereitgestellt. ---------------------------------------------------------------------------- Heightmap-Terrain mit Höhlen ---------------------------------------------------------------------------- Mit dem stencil buffer ? ---------------------------------------------------------------------------- Kram ---------------------------------------------------------------------------- Post So we've got some people who more or less [i]got a bad feeling[/i] about strictly using exceptions always, trying to convince others by funny arguments like 'catch shouldn't be used because goto shouldn't be used' or by providing their own definitions for the term 'exception'. goto is a relict of the pre-procedural programming era, while catch is an up-to-date language feature. And by definition, an exception is just an erroneous situation with which the current function/method cannot cope, [i]fatal for the current scope, but not required to be fatal for the application[/i]. It travels upwards to the caller until it reaches a point where the exception can be handled (or reaches the runtime and thus terminates the application). If an image loading function would not be able to load an image, [i]it cannot resolve the problem itself[/i] and should throw an exception instead. The caller, maybe an RTS game's map, might decide that it can continue without the image, thus it could catch the exception and continue without the image. It works just the same with return values, the only difference is that the compiler won't help you which means you might forget to check the returned value. So, here's my list of compiled arguments: + The compiler does the work for you, so no errors will be accidently ignored + An exception can carry additional error informations + You can derive exceptions to classify your errors + Error handling and normal code is seperated + You regain the ability to freely use return values for other purposes - Exceptions are slower than return values Gültiger XP SP1 key M3RTY-PH3DH-Q82D7-JFCB3-DRHRJ Titel der vermissten DVD: Der 13. Krieger Idee Avatar wird vom Spieler nur grob gesteuert. Vision: Spieler steuert in Richtung, in die Avatar sich bewegen soll. Avatar bewegt sich dabei mit enormer Geschwindigkeit und Eleganz (auf Boden z.B. rennend) vorwärts, jedes Steuern in andere Richtung führt zum sofortigen herumwirbeln, wegspringen oder seiten-beamen. Luft-Taste verursacht absprung zu nächstmöglichem Absprungpunkt (z.B. Baum) und löst eine unaufhörliche Kette von Sprüngen, um in der Luft zu bleiben, aus. Sprung-Taste verursacht irren Luftsprung bei Bodenkontakt. Kampf-Taste löst Hiebattacken aus, bei denen Schwertarm algorithmisch Schwungvoll gewirbelt wird. Feine Mausbewegungen steuern ungefähre Schlagrichtungen. Markierungen zeigen hin und wieder Löcher in der Verteidigung, die sich kurz verfärben, in welchem Augenblick ein Schlag ausgeführt werden muss Schlag-Taste löst Gewalthieb aus, verursacht kurzzeitige Lücke in eigener Verteidigung direkt nach dem Hieb. StorageServer template string getPersistableTypename() { return typeid(T).name(); } template registerPersistable( const shared_ptr &spFactory = shared_ptr( new PersistableFactoryImpl() ) ) { m_Factories.insert( FactoryMap::value_type(getPersistableTypename(), Factory()) ); } void save(shared_ptr &spSerializer, shared_ptr &spPersistable) { SerializerInterceptor(*this, spSerializer).save(spPersistable); } shared_ptr load(shared_ptr &spSerializer) { return SerializerInterceptor(*this, spSerializer).load(); } Persistable *createInstance(const string &sPersistableTypename) { FactoryMap::iterator It = m_Factories.find(sTypename); if(It != m_Factories.end()) return It->second->createInstance(); else throw UnexpectedException("Nuclex::Storage::StorageServer::createInstance()", string("No factory known for class '") + sTypename + "'"); } AudioServer AudioDriver AudioDevice AudioRenderer +setEcho() +setLocation() Sound <-- Repräsentation des Geräuschs selbst SoundSource <-- Geräuschquelle, kann sich bewegen +playSound() Wie mit AudioRenderer übereinkommen ? ---------------------------------------------------------------------------- Design input system ---------------------------------------------------------------------------- + Sollte die Stati vom letzten Abruf bis zum aktuellen Zeitpunkt kennen + Muss mit absolutem Zeitwert arbeiten, um Pausen überspringen zu können ? + advance(float fTime Möglicherweise ist es doch sinnvoll, Subskriptionen zu bestimmten Eingabeobjekten an die Eingabegeräte durchzuleiten. Grund: Wie sich gezeigt hat verursacht das aktuelle System bereits Performanceeinbrüche. Wenn Tasten nur kurzzeitig gedrückt würden, können diese trotz buffered mode verpasst werden, weil sie in einem TriggerState gesetzt und wieder gelöscht würden. Es ist vermutlich nicht ganz leicht, die TimeStamps im buffered mode von DirectInput mit getRunningTime() zu synchronisieren. Grund: Stimmen mit keinem Windows-timer überein. Überlegung, ob nicht via Thread 100x sekündlich gepollt wird und die buffered mode nur für texteingabe (?) und zum erkennen von extrem kurzen klicks und dem Setzen von Controls mit dem modifizierer .down/.up genutzt werden. InputServer typedef std::multimap > ControlMap; void registerControl(const string &sName, const string &sDevice, const string &sTrigger) { DeviceMap::iterator It = m_Devices.find(sDevice); if(It == m_Devices.end()) throw InvalidArgumentException("Nuclex::Input::InputServer::registerControl()", m_Controls.insert(Contro getdevice(d).addControl(c); m_queryList.push_back(c, getModifier(c)); } void removeControl() { getdevice(d).removeControl(c); m_queryList.remove(c); } void heartBeat() { memset(m_stati, 0); for each c in(m_queryList) m_stati[c] = max(m_stati[c], getdevice(c).getControl(c)); } float getControl(c) { return m_stati[c]; } Ist es so erstrebenswert, die Steuerung völlig unabhängig von der Framerate zu machen ? - WinRPG-Problem: Steuerung verliert Präzision, wenn Framerate sinkt - Replays nicht mehr so einfach möglich + Eingabesystem wird enorm vereinfacht Singleton kernel + No more kernel-pointer-give-around chains + Plugin loading / unloading much more transparent - Only one kernel per process ? Subsystems less cleanly seperated ? http://www.delphi3d.net/articles/viewarticle.php?article=impostors.htm ---------------------------------------------------------------------------- Impostors ---------------------------------------------------------------------------- Hi! Ich möchte auf meinem Terrain auch Wälder haben, und zwar richtig grosse mit tausenden von Bäumen, zwischen denen man auch herumlaufen kann. Zwar gibt's bei ATI und nVidia hübsche Techdemos, in denen Gras gerendert wird, aber für so einen Wald taugt das kaum, da die Bäume recht komplexe 3D-Modelle sind und zumal man sie nicht einfach ab einer gewissen Distanz ausblenden kann. Die einzige Möglichkeit, die bleibt, sind also Impostors - d.h. entfernte 3D-Modelle werden nicht nur im LOD reduziert, sondern ab einer gewissen Distanz einfach aus ihrer aktuellen Perspektive auf eine Textur gemalt und dann nurnoch ein einfaches Quad mit der Textur an ihrer Stelle gezeichnet. Ändern sich Kameraposition oder -winkel zu stark, wird die Textur nochmal für die neue Perspektive gerendert. Jetzt überlege ich, welche Art von Impostors am besten geeignet währen: [list][*][b]Impostor-Spheres[/b] - Können an beliebigen Stellen sitzen, komplette SceneGraph-Node könnte zu Impostor werden [*][b]Impostor per 3D-Modell[/b] - Am einfachsten, mit vertex-caching vermutlich sehr performant [*][b]Impostor-Portale[/b] - Auch für Durchgänge in Innenarealen gut geeignet, Texturerzeugung ist aber schwieriger [*][b]Dynamische Impostors[/b] - Keine zusätzliche Arbeit für Leveldesigner, aber mit sicherheit kompliziert, rechenaufwändig und Fehleranfällig[/list] Persönlich halte ich Impostor-Spheres für die mit Abstand beste Möglichkeit. Es sind einfach alle Objekte betroffen, die innerhalb des Radius liegen und die Textur kann einfach als Billboard zur Kamera ausgerichtet werden. Hat schonmal jemand mit Impostors gearbeitet, bekommt man damit auf aktuellen GraKas gute Ergebnisse ? Gibt's vielleicht noch andere Möglichkeiten, meinen Wald zu zeichnen, die ich übersehe ? [VertexBatchSize] (list)(*)Impostor per Objekt (b)+(/b) Es ist genau bekannt, welches Objekt auf den Impostor gerendert werden muss (b)-(/b) Für einen Wald währen immernoch ganz schön viele Impostors nötig. Mit Vertex- und Texturecaching wahrscheinlich verkraftbar ? (*)Impostor-Spheres (b)+(/b) Können an beliebige Stellen gesetzt werden (b)+(/b) Sind sehr einfach umzusetzen, alle Objekt mit Distanz < R werden zum impostor hinzugefügt. (b)+(/b) Würden auch noch gut in den SceneGraph hineinpassen (b)?(/b) Könnte man auch für grössere Dinge, wie Türme, einsetzen. Aber da muss sowieso eine andere Lösung her, um keinen 18-Levelligen, begehbaren Turm mit allen verdeckten Innenarealen als Modell zu haben, der dann grundsätzlich gezeichnet wird (b)?(/b) Kann mir noch nicht vorstellen, wie sich das auf meinen Wald auswirkt, wenn z.B. in der Distanz jeweils 10 Bäume in innerhalb eines Radius als Impostor gezeichnet werden. Vermutlich macht's nicht viel aus. (*)Impostor-Portale / Boxes (b)+(/b) Hervorragend auch für Durchgänge in Innenarealen geeignet (b)+(/b) Durch das Formen von Kästen könnte man ganze Landschaftsgebiete zu Impostors erklären (b)?(/b) Das Erzeugen der Texturen ist nicht mehr ganz so einfach (*)Dynamische Impostors (b)+(/b) Äusserst freundlich zum Leveldesigner, da nichts zu tun ist (b)-(/b) Sehr schwierig, eine Formel zu finden, die sagt, wann und wo ein Impostor eingesetzt werden sollte (b)-(/b) Da Objektdichte und Tiefenunterschiede berücksichtigt werden müssen braucht so eine Umsetzung vermutlich zuviel Rechenzeit (b)-(/b) Sehr Fehleranfällig, da jede kleine Macke bei der Auswahl von Impostor-Bereichen sofort kleine Unstimmigkeiten im Bild produziert Cacheable Surface Image CoronaImage Texture Direct3D9Texture Flaw #1: Surface is mutable through lock()/unlock() and thus not cacheable. Cacheable should provide a method to generate a new unique id in case the object changes Flaw #2: The Image interface is useless and merely serves as grouping facility Flaw #3: Cacheable +getUniqueID() +modified() / assignNewUniqueID() Surface +getSize() +getFormat() +lock() +unlock() +blitTo(LockInfo) +blitTo(Surface) { lock; blitTo(LockInfo); } Image / Bitmap +blitTo(LockInfo) const +blitTo(Surface) const CoronaImage / CoronaBitmap +getCoronaImage() Texture +precache() #select(RenderingContext &) Direct3D9Texture +getDirect3D9Texture() hrtng yu wth pn unknwn t mn -> cmera fls ovr meadws nd frsts "a gme by mrks ewld" (othr fnt) -> cmera shws flyng drgn, lndng n frnt f a cve n a lw mountn "presntd by lnatcsystms" (othr fnt) -> sme blendng effct -> a grp f rdrs is fllwd thrgh a forest "an old rle sys:" "whn mn exprnces fer" -> ridrs rde up mountn whre drgn lnded "no mattr th caus" "agressn wll be th reslt" -> cmera cms t a hlt, bdy piece lnds n th grnd "whn mn notcs he s too wek" "to fght hs fers" -> cmera flys thrgh villge, evntlly shwng wll equippd wrrior "h wll submt hmslf" "nd hpe fr slvatn" -> fde to blck "but smetimes..." -> wrrior knelng dwn n frnt f drgn "fte tkes unxpctd trns" -> wrrior wth crssd bldes stndng wth hs bck t th drgn -> cmera blnds ot whle zomng n hs fce, wrrior scrms -> flshes of slw motn brutl bldy bttle scens -> cmera blnds ot whle flyng awy frm pntng wrrior "Drgn sprt" "Cmng t yr stors.. nevr" http://web.interware.hu/bandi/ranger.html Attributes Skills con |--------+---------| [0] + Weapons str |--------+---------| [1] + Sword spd |--------+---------| [0] o Fencing end |--------+---------| [2] o Blocking dex |--------+---------| [0] o Slashing int |--------+---------| [0] ^ Increase and Decrease bad bad bad with usage, determine how many skill points user needs visible exp number for each kill you're awarded as needs to buy skills like in a warehouse experience points wouldn't work at all SVN_EDITOR=start /w C:\Programme\wscite\SciTE.exe --------------------------------------------------------------------------------------------------- New renderer object model --------------------------------------------------------------------------------------------------- Current rendering resource model is severely fucked up: - Requiring a valid, initialized renderer instance each time a vertex buffer has to be created leads to many shared_ptr<>s pointing to this renderer, making its destruction a tedious task - It's also hard to always somehow pass this pointer around, and not at all convenient - Hot-swapping the renderer or recreating resources is impossible due to the many objects belonging to different owners New model works like this: VertexBuffer, IndexBuffer, Texture, VertexShader and PixelShader are not abstract anymore All these classes store a pointer to an implementation class. When the renderer first sees the resource, it registers its own resource implementation to the resource class and copies its current data over. Need a way to prevent senseless copying around. Instead of signals, just use a pointer, so the implementation class can be silently replacing. Would drastically limit performance if using multiple renderers at once, but at least keep the second memory copy out of the game VertexBuffers could not even become templates, like this: typedef Vertex PlainVertex; typedef VertexBuffer PlainVertexBuffer; PlainVertexBuffer MyBuffer; MyBuffer.access(READONLY)[0] = Vertex(MyPos, Point2(), Color::Red);