Advertisement

Internal or External data tracking?

Started by March 07, 2003 07:06 AM
10 comments, last by Dauntless 21 years, 10 months ago
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?
The world has achieved brilliance without wisdom, power without conscience. Ours is a world of nuclear giants and ethical infants. We know more about war than we know about peace, more about killing than we know about living. We have grasped the mystery of the atom and rejected the Sermon on the Mount." - General Omar Bradley
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 recordstruct unit_status{  vector3 world_position;  vector3 direction;  vector3 terrain_inclination;  terrain_desc terrain_condition;  // etc...  bool under_fire;}; // unit class prototypeclass 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 desiredclass 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!
Advertisement
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.
---New infokeeps brain running;must gas up!
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...)
The world has achieved brilliance without wisdom, power without conscience. Ours is a world of nuclear giants and ethical infants. We know more about war than we know about peace, more about killing than we know about living. We have grasped the mystery of the atom and rejected the Sermon on the Mount." - General Omar Bradley
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.....
The world has achieved brilliance without wisdom, power without conscience. Ours is a world of nuclear giants and ethical infants. We know more about war than we know about peace, more about killing than we know about living. We have grasped the mystery of the atom and rejected the Sermon on the Mount." - General Omar Bradley
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.
Advertisement
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!
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.

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.

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.
The world has achieved brilliance without wisdom, power without conscience. Ours is a world of nuclear giants and ethical infants. We know more about war than we know about peace, more about killing than we know about living. We have grasped the mystery of the atom and rejected the Sermon on the Mount." - General Omar Bradley

This topic is closed to new replies.

Advertisement