I’m trying to learn OpenACC but all the example I’ve studied are quite basics, like move a vector to the GPU, compute some sums or multiplication and bring the results back. What happens if in the code there are other data structures involved? Like for example in C++ I would like to create a class to compute then some data analysis on the toys used by the cats of the people involved in a survey.
“Person” is a class with some details and a vector of all the cats owned (a lot):
class Person {
std::string name;
std::string surname;
std::vector<Cat> cats;
}
Each cat keeps track of all the toys gotten (a lot)
class Cat {
std::string name;
std::vector<Toy> toys;
}
now I want to iterate on all the toys to save data in the class this file is part of for latter analysis, but I’m interested only in the cats owned by the people I got (only reference I have at this point):
for(const auto& cat: person.cats){
insert_cat(cat.name);
for(const auto& toy: cat.toys){
insert_brand(toy.brand)
insert_price(toy.price)
}
}
How can I write this loop using OpenACC and #pragma to run on GPU, where insert_cat, insert_brand and insert_price are functions that given the string it’s then added in some data structure part of this class?
What happens if insert_brand is part of another file, let’s say brands_analysis.hpp, where I want to collect all the brands but it's not part of the current class? How I need to modify the OpenACC code to save that data in an external classes?
You can use the "routine" directive on the Class method's prototype to instruct the compiler to generate device callable functions. The function definitions do not need to be in the same source provided relocatable device code (RDC) is enabled (RDC is enabled by default with nvc++) allowing for device code linking.
Note that insert operations are generally not parallelizable given multiple threads can be inserting at the same time and could cause a race condition if inserting in the same spot.
Also presumably you're using a vector "push" to do the insert. "push" can trigger a reallocation which given the device memory is separate and lead to a mismatch in the data structure on the host and device. I'm also presuming you're using CUDA unified memory, in which case allocation can only be done from the host.
Lists should be built serially on the host and then can be used for computation on the device.
Related
For quite some time I have been interested in perfomance in C ++.
A lot of things keep coming up, whether in conferences or in books:
Do not use a virtual function, have the data in the cache, the branches etc.
There are many benchmarks with examples of video games to show the differences in performance.
The thing is, the examples are always very simple.
How does that really work in code that is more than 20 lines? in AAA video games, finance etc.
If I have 100 types of objects that have different update, different behavior and other joys, it's easy to set up via polymorphism or function pointers.
Now, by following the advice given to make a powerful code, the above options are not possible.
We will therefore prefer to have 100 arrays that we will update separately.
We will have good access to the cache, the functions will probably be inline etc. in short, the performance will in principle be better.
So, I have 100 arrays and 100 differents functions that i will have to call at each frame.
The tables dynamically change depending on what happens, new players appear, a monster dies etc.
Some array will have 0 elements active, others 10...
I would call functions that will have no work (array without active element) but I have no choice, I have to have a flag or look if elements are active or not in my array.
I end up with something like this:
obj1update ();
obje2update ();
....
obj1behavior ();
obj2behavior ();
....
obj1render ();
obj2render ();
.....
objectxy ();
....
Of course, there will undoubtedly be a function which manages all the update calls on the objects, one for the behavior etc but to simplify it gives as above.
To stay in the video game, if the x action of a player causes the arrival of y monsters, there are several types of monsters which have different functions.
We will therefore put the monsters in our tables and this time the functions dealing with these monsters will have work.
We can use the ECS pattern or a derivative but they will potentially have very different behaviors (an ia that directs them or other), different components and therefore different functions will be needed to process them.
They will be called hard in the code since we don't have polymorphism or function pointers and we will have to check at each frame if they have something to process or not.
Is it really done that way? suppose i have 500 types? 1000 ?
Edit:
Lots of comments so I'll get back to you here.
As Federico said, I want to know if these recommendations are good for books but less so in practice.
Several resources that I have looked at:
https://www.agner.org/optimize/#testp
Great suite of several books
www.youtube.com/watch?v=WDIkqP4JbkE&t
Scott Meyers talk on memory
https://people.freebsd.org/~lstewart/articles/cpumemory.pdf
On memory
www.youtube.com/watch?v=rX0ItVEVjHc&t
Data-oriented programming
https://www.dataorienteddesign.com/dodbook/
data oriented design book
There are also other resources but it already gives you an idea on what I'm basing
No, real programs are not written like that. Real programs are written by noticing that all the monsters have a bunch of things in common, and using the same code to do those things. They all pathfind, but they have different distances they can walk? Great. Add a max_walking_distance variable and call the same function each time.
All your monsters have a 3D model? Then you don't need a virtual render method. You can just render the model.
You don't have to divide up your data according to "sensible" boundaries. You don't have to have a struct monster. You can have a struct monster_pathfinding and a struct monster_position and a struct monster_3d_model. Even if you just put these in parallel arrays (i.e. monster 123 has its pathfinding info in monsters_pathfinding[123] and its position in monster_positions[123]) this can make more efficient use of the data cache, because the pathfinding code doesn't load the 3D model pointers into the cache. You can get cleverer by skipping entries if some monsters don't pathfind or don't render. Essentially it is recommended for performance that you group data together according to how it's used, not according to your mental model of the things in the game. Yes, skipping entries makes it way more difficult to delete monsters. But you tick monsters a lot, and you don't delete monsters very often, right?
Maybe only a few monsters shoot guns at the player (the rest try to eat the player). You can have a struct monster_gun_data {int ammunition; int max_ammunition; int reload_time; monster_position *position;}; and then if you have 200 monsters, but only 10 of them have guns, your monstersShootGunsAtPlayers function only has to iterate over the 10 entries in the monster_gun_data array (and load their positions via pointers). Or, you might profile that and find out that because most monsters in your game have guns, it's slightly faster to iterate over all the monsters and check their MONSTER_HAS_GUN flag instead, than to access the position through a pointer which can't be prefetched as easily.
How do you do different kinds of monster attacks? Well, if they're completely different (melee vs ranged), you probably do them with different functions as you have described. Or you might only check the attack type after you decide the monster wants to attack the player. You seem to suggest monsters use different attack code, but I bet this works for almost all of them:
if(wantsToAttack(monster, player)) {
if((monster->flags & HAS_RANGED_ATTACK) && distance(monster, player) > monster->melee_attack_distance)
startRangedAttack(monster, player);
else
startMeleeAttack(monster, player);
}
And what's really different between a monster with a gun, and a monster with a bow and arrow? The attack speed, the animation, the speed the projectile moves at, the projectile's 3D model, and the amount of damage it does. That's all data. That isn't different code.
Finally, if you have something completely different, you might consider making it a "strategy object" with a virtual function. Or just a plain function pointer, if you can. Note that the Monster object is still not polymorphic, because if it was, we couldn't have an array of them and that would slow down all the common code. Only the specific parts of the monster that we're saying are polymorphic are actually polymorphic.
void SpecialBossTickFunction(Monster *monster) {
// special movement, etc
}
// ...
monster->onTick = &SpecialBossTickFunction;
// monster is still not polymorphic except for this one field
You could also do:
struct SpecialBossTickStrategy : TickStrategy {
void onTick(Monster *monster) override {...}
// then you can also have extra fields if needed
// but you also have more indirection
};
monster->onTick = new SpecialBossTickStrategy;
And don't do stuff unnecessarily. Try to be event-driven instead of doing stuff every tick:
// bad because we're calling this function unnecessarily every tick
void SpecialUndeadMonsterTickFunction(Monster *monster) {
if(monster->isDead) {
// do some special reanimation sequence
}
}
monster->onTick = &SpecialUndeadMonsterTickFunction;
// better (for performance)
void SpecialUndeadMonsterTickWhileDeadFunction(Monster *monster) {
// do some special reanimation sequence
if (finished doing whatever) {
monster->onTick = NULL;
}
}
void SpecialUndeadMonsterDeathFunction(Monster *monster) {
monster->onTick = &SpecialUndeadMonsterTickWhileDeadFunction;
}
// ...
monster->onDead = &SpecialUndeadMonsterDeathFunction;
// Also better (for performance)
void DoUndeadMonsterReanimationSequences() { // not virtual at all, called from the main loop
for(Monster *monster : special_undead_monsters_which_are_currently_dead) {
// do some special reanimation sequence
}
}
// Not great, but perhaps still less bad than the first one!
void DoUndeadMonsterReanimationSequences() { // not virtual at all, called from the main loop
for(Monster &monster : all_monsters) {
if(monster.type == TYPE_SPECIAL_UNDEAD_MONSTER && monster.isDead) {
// do some special reanimation sequence
}
}
}
Note that in the third example you have to keep this array special_undead_monsters_which_are_currently_dead up to date. That's okay, because you only have to change it when a monster spawns, disappears, dies, or un-dies. And those are relatively rare events. You are doing a bit more work in these events, in order to save work every tick.
Finally, keep in mind these are techniques that may or may not improve performance in your actual program. I see DOD as a grab-bag of ideas. It doesn't say you must write your program in exactly a certain way, but it is offering a bunch of unconventional suggestions, the theory to explain why they work, and examples of how other people have managed to use them in their programs. Since DOD usually suggests that you complete reorganize your data structures, you may only want to implement it in the performance-critical areas of your program.
Just to add some more perspective on the top-level question:
Big projects that require very good performance really don't use polymorphism?
You're missing out an entire category of polymorphism.
I often mix all three of the below styles in a project, because not all code has the same performance requirements:
setup and configuration code doesn't generally need to be (very) fast. Use OO style and runtime polymorphism all you want for properties, factories, whatever.
Runtime polymorphism broadly means virtual functions.
steady-state code that does need to be fast can often use compile-time polymorphism. This works well for a statically-known (and ideally small) collection of types with similar interfaces.
Compile-time polymorphism means templates (function templates, type templates, replacing the run-time Strategy pattern with the equivalent Policy, etc.)
the code with the tightest performance requirements may need to be data-oriented (ie, designed around cache friendliness).
This isn't all the code in the project, and probably isn't even all the code that needs to be fast. It's all the code that needs to be fast and where performance is dominated by cache effects.
If you only have one copy of an object, you may well inline as much as possible (and try to fit it into the fewest cache lines possible), but splitting it into four different arrays with only one element each won't achieve much.
The problem
The Unreal Engine 4 Editor allows you to add objects of your own types to the scene.
Doing so requires minimal work from the user - to make a class visible in the editor you only need to add some macros, like UCLASS()
UCLASS()
class MyInputComponent: public UInputComponent //you can instantiate it in the editor!
{
UPROPERTY(EditAnywhere)
bool IsSomethingEnabled;
};
This is enough to allow the editor to serialize the created-in-editor object's data (remember: the class is user-defined but the user doesn't have to hardcode loading specific fields. Also note that the UPROPERTY variable can be of user-defined type as well). It is then deserialized while loading the actual game. So how is it handled so painlessly?
My attempt - hardcoded loading for every new class
class Component //abstract class
{
public:
virtual void LoadFromStream(std::stringstream& str) = 0;
//virtual void SaveIntoStream(std::stringstream& str) = 0;
};
class UserCreatedComponent: public Component
{
std::string Name;
int SomeInteger;
vec3 SomeVector; //example of user-defined type
public:
virtual void LoadFromStream(std::stringstream& str) override //you have to write a function like this every time you create a new class
{
str >> Name >> SomeInteger >> SomeVector.x >> SomeVector.y >> SomeVector.z;
}
};
std::vector<Component*> ComponentsFromStream(std::stringstream& str)
{
std::vector<Component*> components;
std::string type;
while (str >> type)
{
if (type == "UserCreatedComponent") //do this for every user-defined type...
components.push_back(new UserCreatedComponent);
else
continue;
components.back()->LoadFromStream(str);
}
return components;
}
Example of an UserCreatedComponent object stream representation:
UserCreatedComponent MyComponent 5 0.707 0.707 0.707
The engine user has to do these things every time he creates a new class:
1. Modify ComponentsFromStream by adding another if
2. Add two methods, one which loads from stream and another which saves to stream.
We want to simplify it so the user only has to use a macro like UPROPERTY.
Our goal is to free the user from all this work and create a more extensible solution, like UE4's (described above).
Attempt at simplifying 1: Using type-int mapping
This section is based on the following: https://stackoverflow.com/a/17409442/12703830
The idea is that for every new class we map an integer, so when we create an object we can just pass the integer given in the stream to the factory.
Example of an UserCreatedComponent object stream representation:
1 MyComponent 5 0.707 0.707 0.707
This solves the problem of working out the type of created object but also seems to create two new problems:
How should we map classes to integers? What would happen if we include two libraries containing classes that map themselves to the same number?
What will initializing e.g. components that need vectors for construction look like? We don't always use strings and ints for object construction (and streams give us pretty much only that).
So how is it handled so painlessly?
C++ language does not provide features which would allow to implement such simple de/serialization of class instances as it works in the Unreal Engine. There are various ways how to workaround the language limitations, the Unreal uses a code generator.
The general idea is following:
When you start project compilation, a code generator is executed.
The code generator parses your header files and searches for macros which has special meaning, like UCLASS, USTRUCT, UENUM, UPROPERTY, etc.
Based on collected data, it generates not only code for de/serialization, but also for other purposes, like reflection (ability to iterate certain members), information about inheritance, etc.
After that, your code is finally compiled along with the generated code.
Note: this is also why you have to include "MyClass.generated.h" in all header files which declare UCLASS, USTRUCT and similar.
In other words, someone must write the de/serialization code in some form. The Unreal solution is that the author of such code is an application.
If you want to implement such system yourself, be aware that it's lots of work. I'm no expert in this field, so I'll just provide general information:
The primary idea of code-generators is to automatize repetitive work, nothing more - in other words, there's no other special magic. That means that "how objects are de/serialized" (how they're transformed from memory to file) and "how the code which de/serializes is created" (whether it's written by a person or generated by an application) are two separate topics.
First, it should be established how objects are de/serialized. For example, std::stringstream can be used, or objects can be de/serialized from/to generally known formats like XML, json, bson, yaml, etc., or a custom solution can be defined.
Establish what's the source of data for generated de/serialization code. In case of Unreal Engine, it's user code itself. But it's not the only way - for example Protobuffers use a simple language which is used only to define data structure and the generator creates code which you can include and use.
If the source of data should be C++ code itself, do not write you own C++ parser! (The only exceptions to this rule are: educational purpose or if you want to spend rest of your life with working on the parser.) Luckily, there are projects which you can use - for example there's clang AST.
How should we map classes to integers? What would happen if we include two libraries containing classes that map themselves to the same number?
There's one fundamental problem with mapping classes to integers: it's not possible to uniquely map every possible class name to an integer.
Proof: create classes named Foo_[integer] and map it to the [integer], i.e. Foo_0 -> 0, Foo_1 -> 1, Foo_2 -> 2, etc. After you use biggest integer value, how do you map Bar_0?
You can start assigning the numbers sequentially as they're added to a project, but as you correctly pin-pointed, what if you include new library? You could start counting from some big number, like 1.000.000, but how do you determine what should be first number for each library? It doesn't have a clear solution.
Some of solutions to this problem are:
Define clear subset of classes which can be de/serialized and assign sequential integers to these classes. The subset can be, for example, "only classes in my project, no library support".
Identify classes with two integers - one for class, one for library. This means you have to have some central register which assigns library integers uniquely (e.g. in order they're registered).
Use string which uniquely identifies the class including library name. This is what Unreal uses.
Generate a hash from class and library name. There's risk of hash collision - the better hash you use, the lower risk there is. For example git (the version control application) uses SHA-1 (which is considered unsafe today) to identify it's objects (files, directories, commits) and the program is used worldwide without bigger issues.
Generate UUID, a 128-bit random number (with special rules). There's also risk of collision, but it's generally considered highly improbable. Used by Java and Unity the game engine.
What would happen if we include two libraries containing classes that map themselves to the same number?
That's called a collision. How it's handled depends on design of de/serialization code, there are mainly two approaches to this problem:
Detect that. For example if your class identifier contains library identifier, don't allow loading/registering library with ID which is already identified. In case of ID which doesn't include library ID (e.g. hash/UUID variant), don't allow registering such classes. Throw an exception or exit the application.
Assume there's no collision. If actual collision happens, it's so-called UB, an undefined behaviour. The application will probably crash or act weirdly. It might corrupt stored data.
What will initializing e.g. components that need vectors for construction look like? We don't always use strings and ints for object construction (and streams give us pretty much only that).
This depends on what it's required from de/serializing code.
The simplest solution is actually to use string of values separated by space.
For example, let's define following structure:
struct Person
{
std::string Name;
float Age;
};
A vector of Person instances could look like: 3 Adam 22.2 Bob 34.5 Cecil 19.0 (i.e. first serialize number of items (vector size), then individual items).
However, what if you add, remove or rename a member? The serialized data would become unreadable. If you want more robust solution, it might be better to use more structured data, for example YAML:
persons:
- name: Adam
age: 22.2
- name: Bob
age: 34.5
- name: Cecil
age: 19.0
Final notes
The problem of de/serializing objects (in C++) is actually big, various systems uses various solutions. That's why this answer is so generic and it doesn't provide exact code - there's not single silver bullet. Every solution has it's advantages and disadvantages. Even detailed description of just Unreal Engine's serialization system would become a book.
So this answer assumes that reader is able to search for various mentioned topic, like yaml file format, Protobuffers, UUID, etc.
Every mentioned solution to a sub-problem has lots of it's own problems which weren't explored. For example de/serialization of string with spaces or new lines from/to simple string stream. If it's needed to solve such problems, it's recommended to first search for more specialized questions or write one if there's nothing to be found.
Also, C++ is constantly evolving. For example, better support for reflection is added, which might, one day, provide enough features to implement high-quality de/serializer. However, if it should be done in compile-time, it would heavily depend on templates which slow down compilation process significantly and decrease code readibility. That's why code generators might be still considered a better choice.
I have a little program that basically prompt questions to user, read the answer, check its validity, and so on, until the creation of an RPG character is fully performed. I rapidly got several classes, each of them related to a bunch of messages to store.
My question is : what is the best way of storing these messages ?
My first (and dirty) try was to have a single file messages.hpp :
namespace M
{
string const message1("what's your name ?");
// + many others
}
but as many classes depended on this file, any tiny change to it implied very long compilation time.
So in order to redistribute the messages, I wanted to include the relevant messages as static members of the classes that needed them, but I found it spoiled a bit readability of my class header. So I was wandering : can I create a subnamespace in a class that will be a message collector like this :
// monster.h
class Monster
{
// members and methods declared here
};
namespace Monster::Messages
{
string const message1("what's your name ?");
// + others messages only used by Monster
}
and could possibly be used like this :
// monster.cpp
Monster::Monster()
{
cout << Messages::message1 << endl;
}
But then, I wondered :
will all the messages of Monster::messages be copied for each instance of the class Monster ?
will all the messages of Monster::messages be copied in all files including monster.hpp (static linkage) ?
Are the messages considered members, static members, or not members at all in Monster ?
What happen if I use the static keyword in Monster::messages, will
it have the signification of static linkage or static member ?
And of course : is it bad practice ? Is there a clever way ?
In my opinion the best practice is to store all those data information in external data file of your game.
I'm going to provide you some benefits of doing so:
Code more clear and light.
Little modifications on data game can be achieved even without compile the binary. That is very important for those data parameters which have to be tested and each time you change the value you have to run the game to see all side effects. (For example some parameters about monster health, damage, GUI position, etc...). In this way the test phase does not require to compile the binary again and it's faster.
Allow other game developers to easily modify game parameters. For example for those persons in the team who are not programmers.
Allow easily to modify game data without change the design in the code. For example let's suppose after two years your game becomes to be very popular and you want to translate it in several languages in order to get more users. In case all those data are embedded in the code, you should re-design and modify your code and, maybe the thing more important, the person who can do that work is only you because only you have the access and the knowledge of the code. In the other case (when the data are stored in external files), more person can easily access and upgrade your game.
How do you can achieve this procedure could be another question on SO, but there are several choices.
For example you can create a unique huge file (that implies some smart tricks, especially for performance reasons) or you can distribute the data parameters in several files.
So your game will have a phase of loading in which the data are read from the disk (as almost all the other games).
Moreover you should think (if you need it) a method in order to crypt those files and avoid game-users can modify them.
I am trying to make an architecture for a MMO game and I can't figure out how I can store as many variables as I need in GameObjects without having a lot of calls to send them on a wire at the same time I update them.
What I have now is:
Game::ChangePosition(Vector3 newPos) {
gameobject.ChangePosition(newPos);
SendOnWireNEWPOSITION(gameobject.id, newPos);
}
It makes the code rubbish, hard to maintain, understand, extend. So think of a Champion example:
I would have to make a lot of functions for each variable. And this is just the generalisation for this Champion, I might have have 1-2 other member variable for each Champion type/"class".
It would be perfect if I would be able to have OnPropertyChange from .NET or something similar. The architecture I am trying to guess would work nicely is if I had something similar to:
For HP: when I update it, automatically call SendFloatOnWire("HP", hp);
For Position: when I update it, automatically call SendVector3OnWire("Position", Position)
For Name: when I update it, automatically call SendSOnWire("Name", Name);
What are exactly SendFloatOnWire, SendVector3OnWire, SendSOnWire ? Functions that serialize those types in a char buffer.
OR METHOD 2 (Preffered), but might be expensive
Update Hp, Position normally and then every Network Thread tick scan all GameObject instances on the server for the changed variables and send those.
How would that be implemented on a high scale game server and what are my options? Any useful book for such cases?
Would macros turn out to be useful? I think I was explosed to some source code of something similar and I think it used macros.
Thank you in advance.
EDIT: I think I've found a solution, but I don't know how robust it actually is. I am going to have a go at it and see where I stand afterwards. https://developer.valvesoftware.com/wiki/Networking_Entities
On method 1:
Such an approach could be relatively "easy" to implement using a maps, that are accessed via getters/setters. The general idea would be something like:
class GameCharacter {
map<string, int> myints;
// same for doubles, floats, strings
public:
GameCharacter() {
myints["HP"]=100;
myints["FP"]=50;
}
int getInt(string fld) { return myints[fld]; };
void setInt(string fld, int val) { myints[fld]=val; sendIntOnWire(fld,val); }
};
Online demo
If you prefer to keep the properties in your class, you'd go for a map to pointers or member pointers instead of values. At construction you'd then initialize the map with the relevant pointers. If you decide to change the member variable you should however always go via the setter.
You could even go further and abstract your Champion by making it just a collection of properties and behaviors, that would be accessed via the map. This component architecture is exposed by Mike McShaffry in Game Coding Complete (a must read book for any game developer). There's a community site for the book with some source code to download. You may have a look at the actor.h and actor.cpp file. Nevertheless, I really recommend to read the full explanations in the book.
The advantage of componentization is that you could embed your network forwarding logic in the base class of all properties: this could simplify your code by an order of magnitude.
On method 2:
I think the base idea is perfectly suitable, except that a complete analysis (or worse, transmission) of all objects would be an overkill.
A nice alternative would be have a marker that is set when a change is done and is reset when the change is transmitted. If you transmit marked objects (and perhaps only marked properties of those), you would minimize workload of your synchronization thread, and reduce network overhead by pooling transmission of several changes affecting the same object.
Overall conclusion I arrived at: Having another call after I update the position, is not that bad. It is a line of code longer, but it is better for different motives:
It is explicit. You know exactly what's happening.
You don't slow down the code by making all kinds of hacks to get it working.
You don't use extra memory.
Methods I've tried:
Having maps for each type, as suggest by #Christophe. The major drawback of it was that it wasn't error prone. You could've had HP and Hp declared in the same map and it could've added another layer of problems and frustrations, such as declaring maps for each type and then preceding every variable with the mapname.
Using something SIMILAR to valve's engine: It created a separate class for each networking variable you wanted. Then, it used a template to wrap up the basic types you declared (int, float, bool) and also extended operators for that template. It used way too much memory and extra calls for basic functionality.
Using a data mapper that added pointers for each variable in the constructor, and then sent them with an offset. I left the project prematurely when I realised the code started to be confusing and hard to maintain.
Using a struct that is sent every time something changes, manually. This is easily done by using protobuf. Extending structs is also easy.
Every tick, generate a new struct with the data for the classes and send it. This keeps very important stuff always up to date, but eats a lot of bandwidth.
Use reflection with help from boost. It wasn't a great solution.
After all, I went with using a mix of 4, and 5. And now I am implementing it in my game. One huge advantage of protobuf is the capability of generating structs from a .proto file, while also offering serialisation for the struct for you. It is blazingly fast.
For those special named variables that appear in subclasses, I have another struct made. Alternatively, with help from protobuf I could have an array of properties that are as simple as: ENUM_KEY_BYTE VALUE. Where ENUM_KEY_BYTE is just a byte that references a enum to properties such as IS_FLYING, IS_UP, IS_POISONED, and VALUE is a string.
The most important thing I've learned from this is to have as much serialization as possible. It is better to use more CPU on both ends than to have more Input&Output.
If anyone has any questions, comment and I will do my best helping you out.
ioanb7
I've built myself a basic EEPROM burner using a Teensy++ 2.0 for my PC bridge, and it's working great, but as I look to expand its compatibility, my code is getting rather hacky. I'm looking for some advice for a proper design for making this code expandable. I've taken a class in software design patterns, but it was awhile ago, and I'm currently drawing a blank. Basically, here's the use case:
I have several methods, such as ReadByte(), WriteByte(), ProgramByte() (for FlashROMs that require a multi-byte write sequence in order to program), EraseChip(), etc. so basically I have an EEPROM pure virtual base class that gets implemented by concrete classes for each chip type that I want to support. The tricky part is determining which chip type object to generate. I'm currently using a pseudo-terminal front-end on the Teensy++ serial input, a basic command-line type interface with parameters, to send options like the chip type to the Teensy++. The question is, is there a design pattern (in C/C++), something like a Factory Pattern, that would take a string input of the chip type (because that's what I'm getting from the user), and return an EEPROM object of the correct derived type, without having to manually create some big switch statement or something ugly like that where I'd have to add the new chip to a list any time I create a new chip derived class? So something like:
public const EEPROM & GetEEPROM(const std::string & id)
and if I pass it the string "am29f032b" it returns a reference to an AM29F032B object, or if I pass it the string "sst39sf040" it returns a reference to an SST39SF040 object, which I could then call the previously mentioned functions on, and it would work for the specified chip.
This code will be run on an AVR microcontroller, so I can't have anything with a huge OOP overhead, but the particular microcontroller I'm using does have a relatively large amount of program flash and work RAM, so it's not like I'm trying to operate in 2kb, but I do have to keep in mind limited resources.
What you're looking for is a Pluggable Factory. There's a good description here. It's attributed to John Vlissides (1 of the Gang of Four) and takes the Abstract factory pattern a few steps further. This pattern happens to also be the architectural foundation of COM.
The usual way of implementing one in C++ is to maintain a static registry of abstract factories. With judicious use of a few templates and static initialisers, you can wrap the whole lot up few lines of boiler-plate which you include in each concrete product (e.g. chip type).
The use of static initialisers allows a complete decoupling of concrete products from both the registry and the code wanting to create products, and has the possibility of implementing each as a plug-in.
You could have a singleton factory manager that keeps a map of string->factory object. Then each factory class would have a global instance that registers itself with the manager on startup.
I try to avoid globals in general, and singletons in particular, but any other approach would require some form of explicit list which you're trying to avoid. You will have to be careful with timing issues (you can't really assume anything about the order in which the various factories will be created).