Sign in to follow this  
Norman Barrows

OO where do entity type definitions go?

Recommended Posts

Norman Barrows    7179

in OO-ish code organization (non-CES), where do entity type definitions go?

 

IE i have say 3 types of entities: orcs, elves, and goblins.  all entities of the same type have the same base hit points, armor class, drawing info etc.

 

where should these constants / data driven variables be stored?

 

would i have a base class called entity, and derived classes for elf, orc, and goblin? the base class would declare the variables, and the derived class contructors would initialize them? but then each instance of an entity type has duplicate type info. 

 

inheritance - B is an A with extra variables and/or methods - doesn't do it.

 

polymorphism - B is an A with some/all methods redefined -  doesn't do it.

 

relational databases does it...   that's how i've been doing with non-OO code.

 

do the same thing (relational databases), but with OO code?

 

 

Share this post


Link to post
Share on other sites
DiegoSLTS    2113

If you want to make something that's pure OOP and want classes for Orc, Elf and Goblin then they should be classes that inherit all from the same base class.

 

Inheritance and polymorfism doesn't force you to add or redefine extra variables of methods, you can make something like:

class A {
    int value;

    A(int value){
        this.value = value
    }
}

class B {
    B():A(2){}
}

and it's completelly valid, but kind of unnecesary.

 

Anyway, if those type of entities have all exactly the same behaviour you really don't need more clases. If you want to make the diferent entities explicit in code you can have a factory class with methods that returns an "Orc", "Elf" or "Goblin". Not an instance of Orc, Elf or Goblin classes, just an instance of the entity class with all the values that represent the correct entity. This way you can do something like:

class Factory {
    static Entity newOrc(){
        return new Entity(/* set the type to orc */);
    }
    static Entity newElf(){
        return new Entity(/* set the type to elf */);
    }
    static Entity newGoblin(){
        return new Entity(/* set the type to globin */);
    }
}

void main () {
    Entity orc = Factory.newOrc();
    Entity elf = Factory.newElf();
    Entity goblin = Factory.newGoblin();
}
Edited by DiegoSLTS

Share this post


Link to post
Share on other sites
Norman Barrows    7179


Entity orc = Factory.newOrc();

 

but that would still put the armor class of an orc in every instance of an orc wouldn't it?  IE my entity instances contain entity type data which is constant between entities of that type (duplicate data).

 

what would be desirable would be to put entity variables in entity instances, and entity type constants in just one place, and have entity instances refer to them. unless the overhead of duplicate entity type data in every entity instance is considered acceptable these days.

Share this post


Link to post
Share on other sites
Washu    7829
If you're data-driving things, then the variables should be coming from your data.

As for where you put the data when you want it in memory... where ever you want. Edited by Washu

Share this post


Link to post
Share on other sites
Washu    7829

As for where you put the data when you want it in memory... where ever you want.

 
so there is no best method then?

In software development, "best" is relative. Edited by Washu

Share this post


Link to post
Share on other sites
Norman Barrows    7179


In software development, "best" is relative.

 

all too true!

 

 

well good old fashioned software engineering says you should not duplicate the entity type data in every instance of an entity, so i guess i'm going to go with the "relational database pattern". <g>

Share this post


Link to post
Share on other sites
frob    44972


well good old fashioned software engineering says you should not duplicate the entity type data in every instance of an entity, so i guess i'm going to go with the "relational database pattern".
In some situations that can be a great pattern.

 

Perhaps not using SQL to do it, but relational database absolutely.  In data-driven games using relational tables of data is a popular and efficient choice.

 

Relational data is rows and columns with a unique key for each row. Hence you have a data table of objects and one entry in the row is the key to the type of monster.

 

Maybe eight of them share the key to "goblin" (data calls it 37), one has a key to "goblin boss" (data calls it 38).  To look up the attributes of the monster follow the relationship, look up in table MonsterData[37] or MonsterData[38], and you're following a relational database pattern.

Share this post


Link to post
Share on other sites
Hodgman    51328

If you want to make something that's pure OOP and want classes for Orc, Elf and Goblin then they should be classes that inherit all from the same base class.

An important rule of OO is to prefer composition over inheritance.
So an OO solution would probably have:
- a Monster class for instances of monster entities in the world, with a pointer to:
- A MonsterDescriptor class containing values that are common to one 'class' of monsters.
- 3 instances of MonsterDescriptor, containing values for Orcs, Elves and Goblins.
- A utility for loading these descriptors from disc, DB, etc.
- Many instances of Monster, which must be passed a MonsterDescriptor in their constructors.

Share this post


Link to post
Share on other sites
_the_phantom_    11250

what would be desirable would be to put entity variables in entity instances, and entity type constants in just one place, and have entity instances refer to them. unless the overhead of duplicate entity type data in every entity instance is considered acceptable these days.


That depends on what you are going for; best performance or saving a trivial amount of memory.

With the type data if you need it while processing some attribute of a single instance then having it in the same block of memory close to the attribute you are processing could well be a net win as, when doing the processing the 'amour class' is likely to be next to, or near, the rest of the data you are manipulating (health = health - max((damage - AC),0); for example) which means the process is likely to have it kicking about in the cache for you to use.

With a 'central database' solution; yes you might save a few bytes but there is a good chance that every time you go to do our example (health = health - max((damage - AC),0); ) you'll crash into a cache miss and the CPU will spin its wheels for a bit while it goes to fetch the data (and unrelated data at that) from somewhere else in memory.

This, of course, might be fine for you, I'm not saying the top method is the absolute total best for all situations; you could have enough type data that your entity definition gets bulked out and it starts causing other performance issues, without knowing the structure of the game, data types or how you act upon them it is hard to say (this is where the joys of hot-cold analysis come into play to work out what data should be together when).

All things being equal I would tend towards solutions which package related data together, by operation, so that on each memory fetch the CPU is pulling in everything I need as together as it can, as it's the latency to memory which hurts most these days given that we are swimming in ALU cycles and memory space.

Share this post


Link to post
Share on other sites
Aardvajk    13207
Generally if the only difference between the enemies is their statistics and models, you shouldn't need different classes. Polymorphism in this context is a tool for implementing different behaviours, not different properties.

Share this post


Link to post
Share on other sites
Norman Barrows    7179


Perhaps not using SQL to do it, but relational database absolutely.  In data-driven games using relational tables of data is a popular and efficient choice.
 
Relational data is rows and columns with a unique key for each row. Hence you have a data table of objects and one entry in the row is the key to the type of monster.
 
Maybe eight of them share the key to "goblin" (data calls it 37), one has a key to "goblin boss" (data calls it 38).  To look up the attributes of the monster follow the relationship, look up in table MonsterData[37] or MonsterData[38], and you're following a relational database pattern.

 

that's how i tend to do everything.   list of entities, and list of entity types. one variable in an entity is the type. and entity_types_list[entity.type] is the type specific data.

 

typically i'll have targets, target types, missiles, and missile types. IE two entity lists. Caveman adds a third list for player controlled characters. but that list does not have a types list to go with it, so its just a flat file database, not relational.

Share this post


Link to post
Share on other sites
Norman Barrows    7179


Many instances of Monster, which must be passed a MonsterDescriptor in their constructors.

 

but that places a copy of the MonsterDescriptor data in every copy of a monster, doesn't it?

 

or does it just set a pointer to MonsterDescriptor?   that would make more sense.   the way my entity.type refers to an entity_type entry. if it sets a pointer, then that's what i'm talking about. 

 

or is the redundant data typically considered a non-issue and i can just go with a flat file and data duplication?

Share this post


Link to post
Share on other sites
Norman Barrows    7179

All things being equal I would tend towards solutions which package related data together, by operation, so that on each memory fetch the CPU is pulling in everything I need as together as it can, as it's the latency to memory which hurts most these days given that we are swimming in ALU cycles and memory space.

 

so cache friendly data driven design should determine data storage patterns. some duplicate data isn't an issue unless MonsterDescriptors are like 100 meg each <g>.

 

cool. that means i can get rid of stuff like

 

// limit turn amount to target type's max turn speed...

if dyr > tgttype[tgt[i].type].turnrate dyr = tgttype[tgt[i].type].turnrate

if dyr < -tgttype[tgt[i].type].turnrate dyr = -tgttype[tgt[i].type].turnrate

 

and replace it with

 

// limit turn amount to target type's max turn speed...

if dyr > tgt[i].turnrate dyr = tgt[i].turnrate;
if dyr < -tgt[i].turnrate dyr = -tgt[i].turnrate;
 
or
 

// limit turn amount to target type's max turn speed...

f_limit(&dyr, -tgt[i].turnrate, tgt[i].turnrate);       // floating point limit (&value, min, max)
Edited by Norman Barrows

Share this post


Link to post
Share on other sites
Krohm    5031

 


All things being equal I would tend towards solutions which package related data together, by operation, so that on each memory fetch the CPU is pulling in everything I need as together as it can, as it's the latency to memory which hurts most these days given that we are swimming in ALU cycles and memory space.

 

so cache friendly data driven design should determine data storage patterns. some duplicate data isn't an issue unless MonsterDescriptors are like 100 meg each <g>.

Sincerely Norman, I have tried your game (in the past, not current beta yet) and I don't think you need to work on the performance side yet. It was already very solid in that regard (and I ran it on oldschool K10 core).

Share this post


Link to post
Share on other sites

Generally if the only difference between the enemies is their statistics and models, you shouldn't need different classes. Polymorphism in this context is a tool for implementing different behaviours, not different properties.

This.

 

 

I would, as a very simplified example, do something like:

struct monster
{
    const char* name;
    int hp;
    int ac;
    int damage;
    script* special_attack;
    // ...
    // mesh* model;
};

struct instance
{
    monster* type;
    int current_hp;

    instance(monster* type_in) : type(type_in), current_hp(type_in.hp) {}
};
...
monster orc = { "orc", 60, 20, 20, nullptr /*orcs have no special attack*/};
monster elf = { "elf", 20,40, 20, &lightning_coming_outa_my_butt };
monster goblin = { "goblin", 5, 5, 5, &cowardish_retreat};

That way, the 753213 orcs in your game that are "just orcs" all use the same template for data (and with the compiler merging string constants they all use the exact same memory for the name, too), whereas a special "hero" monster can have its own stats but can still be based on the stock template (make a copy, add some ability modifiers, and assign a name).

 

As a refinement, you'll probably want to load the monsters from a datafile rather than hardcoding.

Edited by samoth

Share this post


Link to post
Share on other sites
Norman Barrows    7179

Sincerely Norman, I have tried your game (in the past, not current beta yet) and I don't think you need to work on the performance side yet. It was already very solid in that regard (and I ran it on oldschool K10 core).

 

Thanks! i take that as quite a compliment.

 

this is for going forward.   as caveman enters the final stretch, ive started on SIMSpace, my other past best seller. i've already gone so far as to reorganize the code into class like groups, about all i have to do is add:        "class <somename> {    <existing code>  };"

 

Caveman 3 is being developed on a single core 1.3Ghz processor, and its totally un-optimized entity struct and separate de-referenced entity type database can still clock 450Hz (2.22 ms execution time, ~50 entites active).  generating chunks is the only slowdown anywhere (so far) - and its ALMOST tolerable.   personally i'd say that better graphics is probably what caveman needs most next - skinned meshes, most likely, or at least clean up the rigid body limbs, and improve the animal models and animations (some STILL don't gallop! <g>). that and maybe A* for really dense terrain.

 

turns out the change required for relational entity_type database vs MonsterDescritor is trivial. add all the vars in an entity_type to an entity. when you create an entity, copy the type info from the entity_types database (AKA MonsterDescptor Table).  accessing type info then no longer requires the de-reference:   tgt_type[tgt.type].some_type_info, its just: tgt.some_type_info.

Edited by Norman Barrows

Share this post


Link to post
Share on other sites
Norman Barrows    7179

this is the type info struct for an animal from caveman:

 

 
// animal types
struct animaltyperec
{
char name[100];
int num_appearing,hp,atkdmg,rad,AI,meat,bone,trinkets,hides,xp,tohit,
    meshID,texID,avian,standani,walkani,atkani,deadani,can_climb,runani,attack_wav,takehit_wav;
float   speed,
        y_offset,         // dist from object origin to the ground
        scale,
        turnrate;
};
 
 
not very big. i can easily add it to an animal rec.  right now the animals list is sized at 200 active entities max at once. so 200 copies of the struct above isn't a big deal.
 
 
now, check this out......
 
 
this is an entry in the object types database:
 
 
// object types
struct objtyperec
{
// used by all objects
char name[100];
int wt,isliquid,
// used by all weapons.
iswpn,ishandwpn,dmg,wpnskill;
// used by missiles
int rad;
float missilespeed;
// used by projectile launchers
int ammotype,
// used by foods
foodboost,foodmoodboost,
spoilchance,   // dice(10000) chance per hour
spoilrate,  // how much the quality drops
// used for finding and making objects
findtime,       // time in frames between findchecks  /   time in frames to make obj
findchance,     // dice(10000) chance per findcheck   /   dice(10000) chance to make obj
findskill,      // skill that improves from finding   /   skill that improves from making
//used for cooking
fruit_reqd,meat_reqd,water_reqd,spices_reqd,fruitjuice_reqd,vegetables_reqd,nuts_reqd,grain_reqd,qtycooked,
// used for making objects
numskills,numparts,numtools,skill[10],xp[10],part[10],partqty[10],tool[10],toolqty[10],
// used for fishing
fishingbonus,
// used for fixing stuff
canfix,
numASB,   // # of additional skills boosted (above and beyond findskill)
ASB[10],   //  list of additional skilltypes boosted
value,      // trade value. one unit of water = trade value of 1.
taking_angers_god,
god_angered,
herbs_reqd;
requirementsrec r;
Zdrawinfo fix,    // rotation and translation adjustments for 3rd person attack animations
          dinfo;  // drawing info for the object: type (mesh, model, etc), meshID & texID -or- modelID, scale.
int projectile_hit_sfx,  //  used by projectile objects. what wav to play when a projetile hits a target.
    make_sfx,           // what wav to play when the player makes or repairs the object
    obj_in_hand;        // object to draw in hand during make / find anis
};
 
 
 
it includes one requirementsrec struct:
 
 
struct requirementsrec
{
int numskills,numparts,numtools,skill[10],xp[10],part[10],partqty[10],tool[10],toolqty[10],
time,      // in frames usually 
chance,    // dice 10000 chance usually 
numbonusskills,
bonusskill[10],
numboostedskills,
boostedskill[10];
};
 
 
and two zdrawinfo structs:
 
 
 
struct Zdrawinfo
{
int type,         // 0=mesh, 1=model, 2=2d billboard, 3=3d billboard
meshID,       // for models: modelID 
texID,        // for models: aniID 
alphatest,cull,clamp,materialID,rad,cliprng,data[5];
float sx,sy,sz,x,y,z,rx,ry,rz,range;       // range is rng to camera. not currently used.
D3DXMATRIX mWorld;
};
 
 
 
instances of obejcts are stored in an objrec2 struct:
 
// objects list
 
struct objrec2
{
int type,          
    qty,qual,mx,mz,
    data[5],          // storepit: data[0] = pit #.
    active,
    poisoned,
    location,
    raftnum,
    owner;           // npcID, or -1 for player
float x,z,yr;
};
 
 
 
Now....
 
each band member (up to 10 max) has an inventory list of 200 objects, so that's 2000 objrec2's for 10 band members.
 
each storage pit (up to 20 max) has an inventory list of 200 objects, so that's 4000 more objrec2's for 20 storage pits.
 
each NPC (200 max) has an inventory list of 200 objects, so that's 40000 more objrec2's for 200 NPCs.
 
there is a list of 500 world objects (dropped items, player built objects, etc). so thats another 500 objrec2's.
 
each cave, rock shelter, and hut (up to 65,000 max) has an inventory list of 200 objects belonging to the band living there, so that another 13,000,000 objrec2's
 
so that's a total of 13,046,500 objrec2's in the game.   
 
obviously i don't want to replicate an objtyperec, complete with a requirementsrec and two Zdrawinfo's in each of 13,046,500 instances of objrec2's! <g>.
 
so this is definitely a good candidate for the "relational database pattern", right?
 
EDIT:  got my numbers wrong. there are 60,000 caves, 5000 rock shelters, and up to 18,000 huts in the game - 83,000 total - not 65,000 total between the three.  Needless to say, I create those inventory lists on the fly as needed, and page them off of disk. CRH inventory lists, cavern (dungeon) level maps (also generated on the fly as needed), and local map bitmasks are the only things i'm forced to page off disk. everything else is in ram at all times.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
Edited by Norman Barrows

Share this post


Link to post
Share on other sites
Ravyne    14300

A couple ideas come to mind, I didn't have time to read the entire thread before I rush to a meeting, but here are my thoughts.

 

Firstly, if you have shared-per-class data, that's exactly what static class members are for, if the data is constant, all the better but you can't then load it at runtime without subverting the typesystem. If you know it ahead of time and can deal with the trouble of having to edit source to make changes to their values, this is a pretty low-friction solution.

 

Alternatively, a pattern that comes to mind is the Type Traits pattern (this is the pattern used by IO streams in the standard library to define all the different string types, like narrow and wide strings). Essentially, you would create a base class template called something like MonsterTraits<T> that gives you an interface to these shared monster class properties, and then you define N (3 here -- Orc, Elf, Goblin) implementations where T is the Monster Implementation class, so you have MonsterTraits<Orc>, and so on. You can put both constant properties in the Traits interface, as well as properties to be evaluated at run-time (say, a function that depends on one of the constants and some runtime-parameter). Here again, I can't think of a good way to set the values of the properties at runtime (e.g. data driven), but the structure of this approach is similar to PIMPL in that it provides a firewall against changes to MonsterTraits<Orc> having a cascading affect on build-times (and as long as you aren't reaching back to Orc, likewise for changes to Orc) -- the net benefit of that is that you can at least do your data driving at compile-time.

 

I can't think of a good way to do data-driven at runtime except for the basic approach Hodgman outlined (basically, a run-time Type-Traits-style pattern) and I think Frob was alluding to -- but I do want to point out that this is your basic Component-Entity-System, just limited to a single known component variety. Basically, this approach treats Monster as the base Entity, and Orc, Elf, and Goblin are components (The implementation would seem to have some of its relationships inverted, so its not identical, but in concept it seems nearly indistinguishable to me.)

Share this post


Link to post
Share on other sites
_the_phantom_    11250
I'd like to apologise; when I gave you the earlier advice I assumed your data structures would contain the data required for their respective tasks not be.. well... crap.

Apprently we have WILDLY different meanings when it comes to the words 'not very big' as that animal struct is FAR from not being big... because it contains.. well.. crap.

Let me show you what I see;
struct animalTypeRec
{
  char craptakingupacacheline[64];  // 64bytes
  char craptakingupmorecachespace[36];  // 36bytes
  int bunchOfNotPerFrameStuff[22]; // 88bytes
  float heySomethingUsefull;       // 4bytes
  float oppsUnRelatedAagain[2]     // 8 bytes
  float somethingRelatedTo8BytesAgo; // 4 bytes
}
Aka 204 bytes (although the compiler might well pad that to 208 to get 4 byte alignment on the structure size) of which most is crap.
(For the rest of this we are assuming these live in isolation; the problem changes depending on what is around it in an "animal instance" although with the 100 bytes of crap at the start that just means that whatever is before it in memory is going to be pulling in some amount of 64bytes of rubbish on access.)

Now, I've got no idea wtf your update loop looks like (although I do recall a cache missing mess a couple of years ago so I'm going to go with.. hideous) but at a guess I'm going to say that 'speed' and 'turnrate' are the two useful 'frame by frame' values in that structure.

"Speed" lives at an offset of 188 into the structure; CPUs on the other hand fetch cached aligned 64bytes at a time. As we are 180 bytes in the CPU will naturally 'skip' the first 128 bytes as we don't need them and will drop us 128bytes into the data meaning it will read from 'trinkets' onwards in your original structure definition.

Or as I like to think of it [60bytes of crap we don't need][4 bytes of useful].

At which point I'm taking a guess that 'turnrate' will come into play. In this case it is only 8 bytes away which means we need to read in 12bytes + 52bytes of whatever follows (probably the opening 52 characters of crap in animals[1]).

So in order to update ONE creature; we've had to read in 128bytes, of which 8 bytes are useful.
1 in 16 bytes transferred was data we wanted.

By anyone's metric that is terrible.

Welcome to the world of 'hot-cold analysis' wherein you work out which data you need together and split your data structures accordingly.

Firstly, I'd dump the char[100] array; that's a char * to somewhere else, it has no place taking up 100bytes in that structure.
Secondly, 'Number appearing' doesn't seem like per-instance data so fuck it off somewhere else.
Third, arrange things by access if you really must store them in this mess.

At a punt;
struct lessBullShitty
{
  int hp, tohit, takehit_wav;
  int atkdmg, attack_wav;
  
  float speed, turnrate;
  bool can_climb;
  bool avian;

  int rad, AI;  // no idea what these are...
  int xp, meat, bone, trinkets, hides;  // assume these are loot things

  int mesh, texture;
  float y_offset, scale;
  
  int animations[5]; // being lazy, name them if you wish

  char * name;
}
If nothing else your structure is now 96bytes smaller and more logically arranged.

I could do the same on your other structures but frankly the massive one is just making me feel sick even thinking about it to trying to demanage that is a case of taking the Fuck This Train to Nope City.

I will say however don't make a massive "all the fucking things lol" structure for things which don't require it, comments like 'used by food' in with 'used by missiles' is a big blinking sign which says 'warning; this structure is fucked up' which can be seen from space.

I seem to recall taking you to task over your data layout and update loop two years ago on this forum, where I called them out for being piles of shit, so the fact we are here again now with the same bullshit is just frankly annoying.

Share this post


Link to post
Share on other sites
Washu    7829

...

To continue this trend...
// object types
struct objtyperec
{
											// { The garbage that should not be in this struct starts here:
	// used by all objects
	char name[100];							// WHY IS THIS HERE? WHY IS THIS NOT A POINTER? That's 100 BYTES per structure you're forcing a fetch on.
	int wt,isliquid,						// nice naming. Totally useless. Why isn't isLiquid a boolean?
	// used by all weapons.
	iswpn,ishandwpn,dmg,wpnskill;			// Random. Again, booleans? Or a bitset? Or a flags variable?
	// used by missiles
	int rad;								// If it's used by the missiles, why is it here?
	float missilespeed;						// ^^^^^^^^^^^^^^^^
	// used by projectile launchers
	int ammotype,							// If it's used by the projectiles, why is it here?
	// used by foods
	foodboost,foodmoodboost,				// random, no type specified? Using , and newlines is A BAD IDEA.
	spoilchance, // dice(10000)				// Here for what reason?
	spoilrate,  							// ^
	// used for finding and making objects
											// Regarding the comment below... WHAT? TIME IN FRAMES? Don't track time by frames.
		// time in frames between findchecks  /   time in frames to make obj
	findtime,       						// Why is this here?
	
											// WHAT?
		// dice(10000) chance per findcheck   /   dice(10000) chance to make obj
	findchance,								// ... more garbage that doesn't belong here
		// skill that improves from finding   /   skill that improves from making
	findskill,      						// ... more garbage that doesn't belong here
	//used for cooking
											// ... more garbage that doesn't belong here
	fruit_reqd,meat_reqd,water_reqd,spices_reqd,fruitjuice_reqd,vegetables_reqd,nuts_reqd,grain_reqd,qtycooked,
	// used for making objects
											// ... more garbage that doesn't belong here
	numskills,numparts,numtools,skill[10],xp[10],part[10],partqty[10],tool[10],toolqty[10],
	// used for fishing
											// ... more garbage that doesn't belong here
	fishingbonus,
	// used for fixing stuff
											// ... more garbage that doesn't belong here
	canfix,									// ... more garbage that doesn't belong here
	numASB,   // # of additional skills boosted (above and beyond findskill)
	ASB[10],   //  list of additional skilltypes boosted
	value,      // trade value. one unit of water = trade value of 1.
	taking_angers_god,
	god_angered,
	herbs_reqd;
	requirementsrec r;						// } and ends here.
	Zdrawinfo fix,    // rotation and translation adjustments for 3rd person attack animations
	          dinfo;  // drawing info for the object: type (mesh, model, etc), meshID & texID -or- modelID, scale.
	          								// ... more garbage that doesn't belong here
	int projectile_hit_sfx,  //  used by projectile objects. what wav to play when a projetile hits a target.
											// ... more garbage that doesn't belong here
	    make_sfx,           // what wav to play when the player makes or repairs the object
	    obj_in_hand;        // object to draw in hand during make / find anis
};
Edited by Washu

Share this post


Link to post
Share on other sites
Norman Barrows    7179


Firstly, if you have shared-per-class data, that's exactly what static class members are for, if the data is constant, all the better but you can't then load it at runtime without subverting the typesystem. If you know it ahead of time and can deal with the trouble of having to edit source to make changes to their values, this is a pretty low-friction solution.

 

oh that kills!  the language can do it but only hard coded!   yeah - static constant members get stored with the class def and the vmt and such - just one copy. god its been so LONG since i did this oo stuff. how many years ago did it come out? when it came out it was one of those things i learned then promptly forgot. i was just one guy building games, i wasn't CA architect-ing enterprise software that had to be maintained for 30 years. so it seemed like overkill.  i think too many years as a professional engineering student (12 or so?) gave me a knack for learning and forgetting things quickly.

Share this post


Link to post
Share on other sites
Norman Barrows    7179

I'd like to apologise; when I gave you the earlier advice I assumed your data structures would contain the data required for their respective tasks not be.. well... crap.

 

 

ROTFL!

 

yes, its hideous, i know.  its the result of three years of code growth with zero consideration for cache friendliness, no optimization, and no refactoring ever. 

 

you can see some of the first attempts at refactoring in the requirementsrec. after a year or two i noticed that all skills, actions, and objects were beginning to require similar sets of requirements (parts, tools, and skills) to learn, do, make, or find something. so that's a first attempt at some composition. it would eventually evolve into a requirements object that each skill, object type, and action would have. newer code uses requirementsrecs and generic routines that work with the requirementsrec for anything (skill action, or object).

 

 

 

 


I will say however don't make a massive "all the fucking things lol" structure for things which don't require it, comments like 'used by food' in with 'used by missiles' is a big blinking sign which says 'warning; this structure is fucked up' which can be seen from space.

 

 

so you would advocate splitting the object types down further into things like food vs missiles?     but what about when i want to make food a missile so they can have food fights? <g> just kidding!

 

but seriously, you'd advocate splitting things up a bit more?  do remember this is a single instance list of 300 object type defs (currently 273 in use).

 

that's actually only the half of it. i'm finding that for many things (object types, actions, skills), that i end up with these variables like boolean action_causes_fatigue.

 

so then i have a few choices, i can implement a lookup table function using a switch - which i do sometimes, or i can stuff the info into a database - which is the trend for later caveman code, or i can do something else i have yet to think of.

 

so you only see about half of those variables that are specific to only some object types. the rest are implemented as lookup functions.  the database method makes things easier for a later move to data driven if desired.

Edited by Norman Barrows

Share this post


Link to post
Share on other sites
Norman Barrows    7179

char name[100]; // WHY IS THIS HERE? WHY IS THIS NOT A POINTER? That's 100 BYTES per structure you're forcing a fetch on.

 

its a unique string in the game of size char[100].   it has to go somewhere.  and this is an object type definition,  the only time update uses an object type definition struct is to lookup missile initial launch speed, missile collision radius, projectile launcher ammo type (for reloading), and the fixup to draw weapon in hand in 3rd person view.

 

>> // nice naming. Totally useless. Why isn't isLiquid a boolean?

 

old school. i use ints for booleans.

 

>>  iswpn,ishandwpn,dmg,wpnskill;            // Random. Again, booleans? Or a bitset? Or a flags variable?

 

boolean, boolean, base damage, predefined skill type

 

>> // If it's used by the missiles, why is it here?

 

you have to define the collision radius and initial muzzle velocity of a projectile type somewhere. in the object type definitions database seemed the logical place.

 

are you perhaps confusing this object type definition struct with an entity struct? its an object type definition, its not an entity.

 

>> // If it's used by the missiles, why is it here?

 

for each projectile weapon that uses ammo in a game with more than one type of ammo, you must specify on a per weapon basis what kind(s) of ammo it can use.

again, the object definition for the weapon seemed the logical place.

 

>> int ammotype,                           

    // used by foods
    foodboost,foodmoodboost,                // random, no type specified? Using , and newlines is A BAD IDEA.

 

yes. long lists of vars on different lines means you need  to scroll up and down to see what type they are.

 

 

>>      spoilchance, // dice(10000)                // Here for what reason?

    spoilrate,                             // ^

 

once again, more object type definition information. base chance for objects to spoil (assuming they can), and how often the check is made.

 

>> // Regarding the comment below... WHAT? TIME IN FRAMES? Don't track time by frames.

        // time in frames between findchecks / time in frames to make obj

 

fixed frame time - so i can. i could just as easily say time in 66ms chunks. same difference. also, by frame i mean turn, which means one update not one render.

 

so more accurately that comment might read: "number of updates between find checks / number of updates to make object".

 

but by my definition of a frame, 1 frame = 1 turn = 1 update, so that's what it already says - at least by my definition of a frame.

 

i technically define a frame as 1 frame = 1 turn = 1 input, update, and render cycle. i don't define a frame as 1 frame = 1 render. 

Edited by Norman Barrows

Share this post


Link to post
Share on other sites
Washu    7829

 

char name[100]; // WHY IS THIS HERE? WHY IS THIS NOT A POINTER? That's 100 BYTES per structure you're forcing a fetch on.

 
its a unique string in the game of size char[100].   it has to go somewhere.  and this is an object type definition,  the only time update uses an object type definition struct is to lookup missile initial launch speed, missile collision radius, projectile launcher ammo type (for reloading), and the fixup to draw weapon in hand in 3rd person view.

 

The only time this struct is used is to lookup MISSILE LAUNCH SPEED? Furthermore, a unique string of exactly 100 characters? Why? What PURPOSE does it serve? You look stuff up by NAME? This doesn't belong here. You don't need a name, if you do, there are plenty of better ways to deal with this, such as an integer id/index into a string pool.
 

>> // nice naming. Totally useless. Why isn't isLiquid a boolean?
 
old school. i use ints for booleans.

So you're wasting 4 bytes (at the minimum) for a 0 or 1 value.

>>  iswpn,ishandwpn,dmg,wpnskill;            // Random. Again, booleans? Or a bitset? Or a flags variable?
 
boolean, boolean, base damage, predefined skill type

You mean "int, int, int, int". Again, these are separate concerns, why are they in this monolithic structure? Furthermore, if they're bools (and they look like FLAGS to me), and some of them even appear to be mutually exclusive (can you have a weapon that is also a handweapon? or is it weapon or hand weapon?) If it's both, why isn't this a set of flags? An enumeration?. 
 

>> // If it's used by the missiles, why is it here?
 
you have to define the collision radius and initial muzzle velocity of a projectile type somewhere. in the object type definitions database seemed the logical place.

Projectile != food != weapon. Separate concerns.

are you perhaps confusing this object type definition struct with an entity struct? its an object type definition, its not an entity.
 
>> // If it's used by the missiles, why is it here?
 
for each projectile weapon that uses ammo in a game with more than one type of ammo, you must specify on a per weapon basis what kind(s) of ammo it can use.
again, the object definition for the weapon seemed the logical place.

To who? For me the logical place would be to put the missile stuff with the missile concerns. Not to build a giant monolithic structure that holds everything.

>> int ammotype,                           
    // used by foods
    foodboost,foodmoodboost,                // random, no type specified? Using , and newlines is A BAD IDEA.
 
yes. long lists of vars on different lines means you need  to scroll up and down to see what type they are.

So you did it why?

>>      spoilchance, // dice(10000)                // Here for what reason?
    spoilrate,                             // ^
 
once again, more object type definition information. base chance for objects to spoil (assuming they can), and how often the check is made.

If something can't spoil, you probably shouldn't be providing data on how it can spoil, eh?

>> // Regarding the comment below... WHAT? TIME IN FRAMES? Don't track time by frames.
        // time in frames between findchecks / time in frames to make obj
 
fixed frame time - so i can. i could just as easily say time in 66ms chunks. same difference. also, by frame i mean turn, which means one update not one render.
 
so more accurately that comment might read: "number of updates between find checks / number of updates to make object".
 
but by my definition of a frame, 1 frame = 1 turn = 1 update, so that's what it already says - at least by my definition of a frame.
 
i technically define a frame as 1 frame = 1 turn = 1 input, update, and render cycle. i don't define a frame as 1 frame = 1 render. 

Then you're using a different terminology than the rest of the world. Which is another bad idea.

Edited by Washu

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this