Archived

This topic is now archived and is closed to further replies.

Dauntless

Internal or External data tracking?

Recommended Posts

Dauntless    314
I posted something on this in the beginner''s forum to no avail, so maybe I''ll have more luck here. While trying to figure out some coding for communication tests between objects, I came across a design consideration dilemma. Some data isn''t exactly clear whether the data should be intrinsic and internal to the unit, or if the data is external to the unit. The best example is what terrain the unit is on. Should the unit know what terrain it is on at all times because it is an internal data member (perhaps there''s a data member which is a pointer to a terrain object which specifies the terrain type, the map location, elevation, etc)? Or does it make more sense for the unit to poll for what terrain it is on when necessary....say for example when the unit is shot at, while it is moving, etc. Another example might be whether a unit is under fire or not...or what the weather is. Basically any situation which is caused by an external event, and yet which affects a large portion of a unit''s capabilities have a sort of grey area around them. Is it better to keep track of data like this internally or externally? Are there advantages or disadvantages to either approach?

Share this post


Link to post
Share on other sites
Oluseyi    2109
Fudge.

Sometimes certain data seems it should be part of an object definition because, well, that''s what we feel (if A HAS-A B then B should be a [preferrably private] member of A, right?) In reality, though, we sometimes find those crystal clear distinctions blur when we''re faced with massive duplication, data redundancy, access overhead and so forth. So, we fudge. We place the data in an external container - a "manager" of sorts - and give each object access to the data that concerns it within that external database.

Taking the first example (units on terrain), a unit often needs to know where it is and the conditions around it for its autonomous logic. Two solutions exist, both heading in the same direction. The first maintains object-based logic (each unit computes its own actions) but places state information in an external table (could be a class member - static in C++ - if no other entities need direct access to the table) and extracts relevant portions from there. The second approach only simulates autonomous logic by having a "controller" perform all the positioning-based computation for a collection of units, their state information again being maintained in a table external to the units themselves.

Similarly, the unit-under-fire table could be external (granularity is important here; you don''t want a single table for all units in the entire game/battlefield because it''ll take too long to index, particularly with dynamic structures... a good compromise is to attach each table to the smallest logical grouping of units - say a platoon, or whatever the appropriate military designation is ) with units looking up their status for computation rather than checking local variables.

As is my habit in these kinds of threads, here''s a rough code example in C++:

// unit_status record
struct unit_status
{
vector3 world_position;
vector3 direction;
vector3 terrain_inclination;
terrain_desc terrain_condition;
// etc...
bool under_fire;
};
 
// unit class prototype
class unit;
 
// unit_aggregation (maintains unit_status recordset)
// unit_aggregation may represent a platoon, squadron, battalion or a single
// unit separated from his peers - whatever level of granularity is desired
class unit_aggregation
{
public:
// interface...
 
private:
// unit_id is an assumed unique identifier within this aggregation
// unit_status_ptr is a smart/shared pointer to a unit_status, automating memory
// management concerns with dynamically allocated unit_status-es
// (preferrably pool allocated)
std::hash_map< unit_id, unit_status_ptr > unit_infobase;
 
// other data...
};

It should be pretty straightforward to determine how this unit_aggregation structure meets the requirements of the situation.

On additional point: unit_aggregation is almost certainly an implementation of a well-known design pattern, though I couldn''t tell you which. It''s worth your time to learn about design patterns, and to memorize or keep handy references to common ones, especially as you approach ever-more-complex projects.

Cheers!

Share this post


Link to post
Share on other sites
Flarelocke    410
There are three approaches that I would use, in order of preference (i.e. if there is no clear answer for one, I drop to the next one):
1. The most efficient way. Storing on the unit makes the size of each unit bigger. Not storing it on the unit makes determining computationally intensive. (probably something like O(log2 n)) where n is the size of the map.

2. Whichever makes the most conceptual sense. (in your example, I''d poll the terrain)

3. Whichever I design first. Now, design doesn''t necessarily mean code, but I think you know what I mean.

I suppose another approach would be to store data on both and have some way of making sure changes to one flow to each of its dependents, but that''s probably more complicated than necessary.

Share this post


Link to post
Share on other sites
Dauntless    314
quote:
Original post by Oluseyi
...The first maintains object-based logic (each unit computes its own actions) but places state information in an external table (could be a class member - static in C++ - if no other entities need direct access to the table) and extracts relevant portions from there.


This I understand and was thinking of the unit having a pointer to World_Data object which was essentially a table of relevant information. A public access method could then look up the World_Data object to retrieve the relevant info and plug the return values into any required function parameters...(for example, when a unit moves, it might receive terrain bonuses or penalties, so the Move function would take the terrain type as a function parameter).

quote:
Original post by Oluseyi
The second approach only simulates autonomous logic by having a "controller" perform all the positioning-based computation for a collection of units, their state information again being maintained in a table external to the units themselves.


I''m not sure I understand the concept of a "controller" here. What''s different between this and the first approach?

quote:
Original post by Oluseyi
Similarly, the unit-under-fire table could be external (granularity is important here; you don''t want a single table for all units in the entire game/battlefield because it''ll take too long to index, particularly with dynamic structures... a good compromise is to attach each table to the smallest logical grouping of units - say a platoon, or whatever the appropriate military designation is ) with units looking up their status for computation rather than checking local variables.


I was thinking to group it exactly like that. It''s sort of a hassle having individual objects that in turn comprise whta the player actually plays with...a logical grouping of units. But the individual objects play an important role in the functioning of the group. I hope players get used to the idea that when they order a Commander to have a "platoon" do something, it''s actually a group...not a single entity. It acts as a single entity, but each constituent within the group affects the group as a whole. So if for example, the vehicle with the AI Commander in it gets destroyed, the rest of the platoon is fine, but it suffers some drastic short term consequences. So I was thinking of having look-up tables at the Cluster level...though I may play around with small Battle_Group levels (which are essentially a sub-class of a Cluster class that instead of holding individual objects, hold other Clusters).

As is my habit in these kinds of threads, here''s a rough code example in C++:

quote:
Original post by Oluseyi
struct unit_status
{
vector3 world_position;
vector3 direction;
vector3 terrain_inclination;
terrain_desc terrain_condition;
// etc...
bool under_fire;
};


Should this be a union? Let''s say that a platoon is made up of 4 tanks. While each individual object isn''t exactly in the same spot, generally speaking, since you play with the platoon as a whole, could this be shared data for the 4 tanks within the platoon?

Once I''m through with Bruce Eckel''s two books on C++ I''m going to try to find some design theory books. I''ve been reading "Linux 3d Graphics Programming" by Norman Lin, and he keeps referring to design methodologies, software patterns, and OO design. Unfortunately, I find 3d graphics a bit tedious but it''s helping me learn alot about software design as a whole. I''ll probably pick up a book on Python or Lisp too very shortly...so my reading time is going to be full (argghh, no time to re-read Return of the King before it comes out probably...)

Share this post


Link to post
Share on other sites
Dauntless    314
Now that I really think about it, almost every example I can think of which blurs the line between internal or external data tracking is really about knowledge.

I guess it''s essentially state data about the world that the unit (or Commander) should know. So even though the actual data is "external", it becomes internalized by the object so it can be used for autonmous logic.

Therefore it seems to me to make more sense to have "knowledge" data internalized by the unit so that it can use it for AI purposes. But as Flarelocke says, this will be making the units and Commander objects rather large. And I also wonder, since the knowledge "database" has to continually be updated, wouldn''t this be more of a drain on computing resources? I suppose each object could have a logic loop that maintained the state data until an external event happens to trigger an event, thereby causing the sate data to change. But that seems awfully compute intensive.

But on the other hand, let''s say World_Data is kept external to the object. Now, let''s say that a unit is moving, shooting, being shot at, and trying to communicate with its Commander. Every function call will have to in turn poll to see what terrain it is on...meaning 4 seperate function calls. If however I internalize this data, it already knows what terrain it''s in.

hmmmmm.....

Share this post


Link to post
Share on other sites
Diodor    517
quote:

Original post by Dauntless

But on the other hand, let''s say World_Data is kept external to the object. Now, let''s say that a unit is moving, shooting, being shot at, and trying to communicate with its Commander. Every function call will have to in turn poll to see what terrain it is on...meaning 4 seperate function calls. If however I internalize this data, it already knows what terrain it''s in.



Make the members of World_Data public and have your Unit objects access it directly.

Share this post


Link to post
Share on other sites
Oluseyi    2109
quote:
Original post by Diodor
Make the members of World_Data public and have your Unit objects access it directly.

No!

If two classes have a blurry distinction of data ownership, you can ameliorate the overhead by making them friends of each other, meaning data is still appropriate hidden but directly available to the friend class. In this case, I''d still not recommend that method, instead opting to provide public inlined methods for manipulating private data (the accessor methods should operate at higher granularity - retrieving the entire unit state as opposed to just terrain data, for example - and provide an opportunity for validation).

Do not lightly disregard encapsulation and data hiding. They are fundamental tenets of OOD because they make the project more maintainable in the long run. Imagine when you decide to write a sequel and need to modify the relationships between two classes. If you''ve adhered to these tenets, the logic for certin attributes is properly local to one class, whereas failure to do so means you have to excavate multiple classes to salvage the implementation of a single attribute.

quote:
Original post by Dauntless
Now that I really think about it, almost every example I can think of which blurs the line between internal or external data tracking is really about knowledge.

I guess it''s essentially state data about the world that the unit (or Commander) should know. So even though the actual data is "external", it becomes internalized by the object so it can be used for autonmous logic.

This is a correct perceptual assessment, but it presents interesting implementation questions. What happens in the real world - and what a user perceives in a virtual one - are not necessarily the way things unfold in code terms. Efficiency and performance often dictate that we take alternative, yet still logical, approaches to solving the problem.

quote:
Therefore it seems to me to make more sense to have "knowledge" data internalized by the unit so that it can use it for AI purposes. But as Flarelocke says, this will be making the units and Commander objects rather large. And I also wonder, since the knowledge "database" has to continually be updated, wouldn''t this be more of a drain on computing resources? I suppose each object could have a logic loop that maintained the state data until an external event happens to trigger an event, thereby causing the sate data to change. But that seems awfully compute intensive.

Break down the use of "knowledge." I estimate that most of the time the units will function collectively, requiring little individual AI computation. In such instances, it makes sense to use collective information, which would logically be external to the individual units. When individual AI computation becomes necessary - mostly during combat/conflict, information can be delegated to each unit and computation ramped up (while collective computation ceases, balancing processing load). In essence, this is a session-based architecture.

I''d recommend reading a few post-mortems at GamaSutra on games that featured similar requirements. Midtown Madness 2 had pedestrians who needed to go about their business when unthreatened but take evasive action when you veered towards them, as well as other vehicular traffic. There''s a GamaSutra post-mortem (you''ll have to search; don''t have the link handy) which details a lot of the implementation, and you''ll find that many of the data required for autonomous individual computation were stored externally (street information, for example).

Good luck!

Share this post


Link to post
Share on other sites
Oluseyi    2109
quote:
Original post by Dauntless
The second approach only simulates autonomous logic by having a "controller" perform all the positioning-based computation for a collection of units, their state information again being maintained in a table external to the units themselves.

I''m not sure I understand the concept of a "controller" here. What''s different between this and the first approach?

Rather than having AI "performed" by each unit actually housed as a method in the unit class, it is a method in the unit aggregation class that iteratively computes motion trajectories and so forth for all of the units it aggregates into a collection.

quote:
Original post by Dauntless
struct unit_status
{
vector3 world_position;
vector3 direction;
vector3 terrain_inclination;
terrain_desc terrain_condition;
// etc...
bool under_fire;
};


Should this be a union? Let''s say that a platoon is made up of 4 tanks. While each individual object isn''t exactly in the same spot, generally speaking, since you play with the platoon as a whole, could this be shared data for the 4 tanks within the platoon?

Hmm. No, I don''t think so. Each unit in the platoon or whatever has its own position, direction and terrain data, and this structure just conveniently wraps them all together so they can easily be passed from the aggregation to the units themselves (one call instead of four). I would recommend giving the unit a copy of the data to manipulate, and only having the unit request the aggregation to update its data if it survives the conflict (so we don''t needlessly constantly "check in" data for doomed units).

Software methodologies and design patterns are the two greatest benefits I''ve received from a CS degree. They''ve made me a better and more productive programmer in ways I never would have imagined. I heartily recommend that you investigate them.

Share this post


Link to post
Share on other sites
Diodor    517

quote:

Original post by Oluseyi

No!

If two classes have a blurry distinction of data ownership, you can ameliorate the overhead by making them friends of each other, meaning data is still appropriate hidden but directly available to the friend class. In this case, I''d still not recommend that method, instead opting to provide public inlined methods for manipulating private data (the accessor methods should operate at higher granularity - retrieving the entire unit state as opposed to just terrain data, for example - and provide an opportunity for validation).

Do not lightly disregard encapsulation and data hiding. They are fundamental tenets of OOD because they make the project more maintainable in the long run. Imagine when you decide to write a sequel and need to modify the relationships between two classes. If you''ve adhered to these tenets, the logic for certin attributes is properly local to one class, whereas failure to do so means you have to excavate multiple classes to salvage the implementation of a single attribute.



Whenever a class accesses data from another class, the project becomes more "connected", and as such less elegant, less maintainable. Whenever a class needs information from another class, a decision must be made whether to allow it (at the expense of elegance) or not (at the cost of redesign). The quality of the program depends on how good these decisions are, and less on the communication protocols between classes.

I agree my sugestion was in poor style, but this thread discusses a non-problem. The decision that the Unit class needs to access data in the World_Data class is already made, the damage is done. Whatever the communication protocol is used to talk between these classes (direct public access, direct access using friend classes, read-only access through inlined functions, with or without the copying of this data to the Unit Class), the important decision is already taken.

If later in the project a decision is made that some class needs to read or even modify members of the World_Data class, no private modifiers will stop the determined programmer from letting the other class modify these members. Strongly enforcing the access rights between classes using C++ features or using simple comments to achieve the same thing are a matters of estethics, not substance.

Share this post


Link to post
Share on other sites
Dauntless    314
Heck, I wish I had a CS degree....I''m totally self-taught about computers. From networking, to hardware to programming I''ve pretty much studied on my own. And while self-study is great at teaching you what NOT to do (when you fudge up and realize you need to do something different) it doesn''t really tell you the right way to do something the first time either.

But I''m gaining some really good insights here. I''m still grappling with the concepts of public and private access methods. When I first learned them, I was like, "why bother with the indirect access of setting private member data or calling private member functions?". To be honest I still have a hard time understanding data hiding and encapsulation. I realize that there are essentially two kinds of programmers....clients that use libraries and objects, and designers who actually build the libraries. I guess my problem in understanding data hiding is figuring out why the designer doesn''t always want the client to know how certain code works...i.e. the private interfaces.

As for the friend vs. public access communication methods I''m not really sure what would suit my game better. In some ways, being a friend makes more logical sense, since one class is closely related and works together with the other class (the unit class and the World_Data class). On the other hand, I''m not sure if having a public call to Unit::CommTest() would have any "fragile" logic associated with it. The only disadvantage I can see is that if like Oluseyi said, by having new add-ons with new features that utilize different code to work...I guess it might be a hassle. I had originally thought of doing it like Diodor said because it''s easier for me to think in terms of public access, but now that Oluseyi mentioned the friend access method, it would allow for better data hiding.

Share this post


Link to post
Share on other sites
Oluseyi    2109
quote:
Original post by Diodor
Whenever a class accesses data from another class, the project becomes more "connected", and as such less elegant, less maintainable. Whenever a class needs information from another class, a decision must be made whether to allow it (at the expense of elegance) or not (at the cost of redesign). The quality of the program depends on how good these decisions are, and less on the communication protocols between classes.

That''s not quite accurate. The choice of communication protocols is a direct function of the quality of decisions made with respect to the level of cohesion and coupling between the classes - a design decision.

quote:
If later in the project a decision is made that some class needs to read or even modify members of the World_Data class, no private modifiers will stop the determined programmer from letting the other class modify these members. Strongly enforcing the access rights between classes using C++ features or using simple comments to achieve the same thing are a matters of estethics, not substance.

I completely disagree. Strongly enforcing access rights are fundamental design decisions, effected to insulate client code from library changes (interface consistency). If class A uses class B and class B''s implementation is constantly changing (due to new techniques or whatever) but its interface remains the same, then class A does not need to be modified, or even recompiled if class B instances are being dynamically retrived from a shared library (DLL/.so). If, OTOH, it had been decided that class A should reach across class boundaries (because class B made data public to class A) and class B changes, then class A needs to be modified - which can become a maintenance nightmare.

There is a reason for public, private and protected access in C++. There is a reason for packages in Java and the corresponding concept in UML and other modeling tools. Classes within the same package are considered to be co-features, and therefore should be the only other classes ever given access to private data and methods (since packages are released at a consistent version number, all the classes within it must be maintained together). Any package B only uses a lower-level package A through public classes and their published interfaces (note that Java also has private classes, only visible within the package), and this strict stratification helps to develop reusable components which accelerates the overall software engineering process in the long term.

quote:
Original post by Dauntless
Heck, I wish I had a CS degree....I''m totally self-taught about computers. From networking, to hardware to programming I''ve pretty much studied on my own. And while self-study is great at teaching you what NOT to do (when you fudge up and realize you need to do something different) it doesn''t really tell you the right way to do something the first time either.

I feel you. I was completely self-taught (and I''d like to think I''d done a good job) before I transferred to UNCG, though I read as many books as I could get my hands on. But even still, there was so much material that I never would have been introduced to had I not taken CS classes in college. My suggestion is for you to go to your local community college if a CS degree just isn''t feasible and enroll for a couple of classes (advanced data structures, software engineering). You''ll be glad you did.

quote:
But I''m gaining some really good insights here. I''m still grappling with the concepts of public and private access methods. When I first learned them, I was like, "why bother with the indirect access of setting private member data or calling private member functions?". To be honest I still have a hard time understanding data hiding and encapsulation. I realize that there are essentially two kinds of programmers....clients that use libraries and objects, and designers who actually build the libraries. I guess my problem in understanding data hiding is figuring out why the designer doesn''t always want the client to know how certain code works...i.e. the private interfaces.

Insulation from change. If I design a library - say, libOluseyi - and you''re using it in your application, how happy would you be if with every new version you had to rewrite your code because mine changed? By using abstraction and data hiding, you don''t know or care how my methods are implemented, and I write methods at a higher level of abstraction than the basic operations I perform - InitializeNetworkConnection() as opposed to CreateSocket(), GetSocketProperties(), etc. This way, even when I completely change the internal workings of my socket library (say, switching from BSD sockets to WinSock2 on Win32) your code doesn''t need to change. All you''ll do is recompile or relink - in some cases not even that, if I provide libOluseyi in a DLL for example - and you''re done.

quote:
As for the friend vs. public access communication methods I''m not really sure what would suit my game better. In some ways, being a friend makes more logical sense, since one class is closely related and works together with the other class (the unit class and the World_Data class). On the other hand, I''m not sure if having a public call to Unit::CommTest() would have any "fragile" logic associated with it. The only disadvantage I can see is that if like Oluseyi said, by having new add-ons with new features that utilize different code to work...I guess it might be a hassle. I had originally thought of doing it like Diodor said because it''s easier for me to think in terms of public access, but now that Oluseyi mentioned the friend access method, it would allow for better data hiding.

I honestly think the external database method is the best option, but I leave the choice to you. Nothing teaches better than experience - not even after a Ph.D. At the very least, try the various architectures on a small scale and see which requires the least code, which provides the most security (in terms of protection from programmer error), which is the most scalable, which is the most maintainable...

Good luck!

Share this post


Link to post
Share on other sites
rmsgrey    153
An example off the top of my head to illustrate why encapsulation can be useful: the unit needs to know what local visibility is like - a value that properly belongs to the terrain (based on ground cover and elevation) so it need to obtain the (precalculated) value. In case A, the visibility is made directly accessible to the unit (friend or public). In case B, a public method (GetVisibility()) is created.
Locally variable weather effects are then implemented, so each interval, all terrain objects have their local weather and visibility updated. No problems so far. Then the decision is made that the calculation of weather effects on visibility is slowing the game down too much. To get round this, calculation of visibility is only made when required. In case A, this means changing the unit code to call terrain.UpdateVisibility() each time it accesses terrain.visibility. In case B, this simply means moving the call to UpdateVisibility() from SetWeather() to GetVisibility(), without changing the unit class at all. Not the greatest of differences, but it''s more likely that you''d miss an access in case A than that you''d overlook the single copy of GetVisibility in case B...

Share this post


Link to post
Share on other sites