New OOP Framework using C

K

Kamilche

I have looked at many object-oriented programming frameworks out there
for C. Though the ideas presented are intriguing, and I've used some
of them in my own work, they all suffered some drawback or another.

I have created a new method of doing OOP with C, something that fits
my needs particularly well. I haven't encountered anything else like
it, so I'm tossing it out to the Internet for comment.

http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=6820&lngWId=3

Here's a bit more info about it, in case you're interested:

This framework allows for object oriented processing in C.
Instead of structs with hard-coded fields, every object is a
hash table that can contain an infinite number of name/value pairs,
called 'stats' from here on out. So - instead of this:

struct PERSON_T
{
char *name; // filled with 'Harry
int age; // filled with '17'
}

you'd have this instead:

HASHTABLE (list of name/value pairs)
StatName StatValue
-------------- --------------
name Harry
age 17

(The stat names are actually stored as enums, for maximum lookup
speed.)

Each type of object that can exist, such a 'square' or 'circle,'
must have an entry in the master table, that sets the 'default field
values' for that type of object. This entry is called a 'master item.'
Each master item in the master table is itself a hash table, so the
master table can be considered a 'table of tables.'

When accessing the fields of an object, use GetStat or SetStat.
They work in the following manner:

* GetStat - Retrieves the value from the object, if that
name/value pair exists.
If it doesn't, retrieves the value from the
master table for that object type.

* SetStat - Sets the value in the object only -
never modifies the master table!

This is an 'auto-optimizing' memory scheme. Only the differences
between the object, and the 'master object' for that type, are stored.
This allows you to create millions of items in memory, with thousands
of stats each, in a very small amount of space.
If the stat exists in the object, it is pulled from the object.
If it doesn't, it assumes 'the default value' from the master is
good enough, and uses that instead.

Constructing objects is easy - just call the Create routine with the
name of the master object, plus all the stats you want to override.

In addition to storing the default stats, the master table stores
function pointers to event handlers for that object.

Look at the following master item example:

CIRCLE
StatName StatValue
---------- --------------
Type 200
Name CIRCLE
Inherits SHAPE
Radius 1
OnDraw circle_draw
OnSetRadius circle_setradius

This master item describes a new object type called 'CIRCLE', that
has a type of 200, a name of CIRCLE, and a default radius of 1.
When events are sent to the object, it looks for the corresponding
'OnEvent' stat for the specified event, and calls the function
through that function pointer. For instance - when the 'draw'
command is sent to it, it pulls up the 'OnDraw' stat, and picks out
the function pointer. Then, it executes the 'circle_draw' routine
through that function pointer. When a 'setradius' command is sent
to it, it executes the 'circle_setradius' routine.

It also illustrates the 'inheritance' feature. Inheritance comes into
play only when loading the master file. When an 'Inherits' stat is
encountered, the master loading routine pulls up the specified master
item, and pulls all ITS stats out and adds it to the current master
item.
In this case, the master table entry for 'SHAPE' looks like this:

SHAPE
StatName StatValue
---------- --------------
Type 100
Name SHAPE
Inherits BASE
OnMove shape_move
OnSetName shape_setname

All stats except type and name will be added to the CIRCLE master
item.
Though not illustrated, you can inherit from multiple base types.
Simply specify multiple INHERITS stats when loading the master file.
The stats will be pulled in order, with newer entries overlaying older
entries, so be careful to specify the inheritance loading order
accordingly!

After all the inheritance is performed, the CIRCLE item actually looks
like this in memory:

CIRCLE
StatName StatValue
---------- --------------
Type 200
Name CIRCLE
Radius 1
OnDraw circle_draw
OnSetRadius circle_setradius
OnMove shape_move
OnSetName shape_setname

As you can see, 'draw' and 'setradius' are specific to the circle
master item. 'move' and 'setname' are inherited from the 'shape'
master item. (The 'inherits' stat is missing entirely, as that is
only used when loading the master table into memory.)

All method calls are done via 'Do'. Do takes the item,
the event, and any additional arguments. It looks up which stat is
associated with the event, and retrieves that function pointer.
If the function pointer exists, it executes it with the supplied
arguments. If it doesn't exist, it returns an ERR_UNHANDLED_EVENT
error.

All possible events must be listed in the 'events.txt' file, and
all possible stats must be listed in the 'stats.txt' file.
All the errors are listed in the 'errors.txt' file.
A stringizing macro STRINGIZE is used to turn these into the
appropriate enum and arrays in code.

That's about it. I've stripped this down from my own code to its
bare essentials. In my code, I load the master table from disk, and
so should you! This makes it easy to modify functionality of objects
without cutting any code. I also have specialized 'load from disk' and
'save to disk' handling for the objects, not implemented here.
As you might guess, it's only necessary to store the stats from the
object hash table, not the master item hash table.
The master table should only ever be modified 'by hand'.

I hope you find some of these concepts useful in your own work!
If you do any enhancements, drop me a line.

--Kamilche, (e-mail address removed)
 
I

Ivan Vecerina

Hi!
Kamilche said:
I have looked at many object-oriented programming frameworks out there
for C. Though the ideas presented are intriguing, and I've used some
of them in my own work, they all suffered some drawback or another.

I have created a new method of doing OOP with C, something that fits
my needs particularly well. I haven't encountered anything else like
it, so I'm tossing it out to the Internet for comment. [....] object representation:
HASHTABLE (list of name/value pairs)
StatName StatValue
-------------- --------------
name Harry
age 17

This looks very much like the way Perl, Python, and some similar
scripting languages represent objects. A good idea indeed.

A question is: if such an approach is needed in large portions of
your software, maybe the use of one of these existing languages
could be considered (knowing that C modules also can be interfaced
to them).

If not, you may still want to inspect the implementation of these
languages, and compare their approach to yours...


Regards,
Ivan
 
B

Bruno Desthuilliers

Kamilche said:
I have looked at many object-oriented programming frameworks out there
for C. Though the ideas presented are intriguing, and I've used some
of them in my own work, they all suffered some drawback or another.

I have created a new method of doing OOP with C, something that fits
my needs particularly well. I haven't encountered anything else like
it, so I'm tossing it out to the Internet for comment.

http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=6820&lngWId=3

Here's a bit more info about it, in case you're interested:

This framework allows for object oriented processing in C.
Instead of structs with hard-coded fields, every object is a
hash table that can contain an infinite number of name/value pairs,
called 'stats' from here on out. So - instead of this:

struct PERSON_T
{
char *name; // filled with 'Harry
int age; // filled with '17'
}

you'd have this instead:

HASHTABLE (list of name/value pairs)
StatName StatValue
-------------- --------------
name Harry
age 17

Congratulations, you just reinvented [Python|Perl|PHP|Javascript] !-)

Is this conscient inspiration, or just a coïncidence ?

(Ok, now I go and read the rest of the post...)

Bruno
 
K

Kamilche

Ivan Vecerina said:
This looks very much like the way Perl, Python, and some similar
scripting languages represent objects. A good idea indeed.

Ah, I see what you mean. I know Python and Lua, and there are some
similarities. Mine has 2 things that those languages do not, that I
think they could benefit from:

1) The memory handling really looks unique to me. I'm creating an
online game where there will be THOUSANDS of items that are very
similar - pairs of pants, apples, tables. If they do differ, it will
only be relatively infrequently, by a small amount - a person might
rename their sword, for instance. By storing only the differences
between the base object and the instance, my method of storage results
in vast CPU and storage savings. Using it, I can store a million items
in 181 megs of memory... both in memory, and on the hard drive. Using
a typical 'unrolled' layout, would require 2 gigs memory and hard
drive space. The footprint for an object is only 190 bytes - with
that, the hash table has a starting size that allows up to 8 'unique'
or overridden stats. As more stats are overriden, the size will
increase.

2) The ability to totally customize an object from text files. This
allows you to reload it dynamically at runtime, and change an object's
behavior without cutting code, in a manner which is more flexible and
doesn't result in 'duplicate' fields, unlike C++!

Here's a typical example used in C++ texts:

Animal object - has skin color, implements 'walk' and 'talk.'
Horse object - inherits 'animal', overrides 'talk' with 'neigh.'
Bird object - inherits 'animal', adds 'fly', overrides 'talk' with
'chirp.'
Pegasus object - inherits from both horse and bird.

In C++, this multiple inheritance results in the 'skin color' stat
being inherited from both horse and bird. This makes the structure
weighty, and adds such a complication to programming, that most
programmers just avoid multiple inheritance.

Using the method I devised, the fields aren't duplicated, they're
'overlaid', and the last one in gets to set the default value. If you
create a 'pegasus' and specify 'inherits: horse' and 'inherits: bird',
the horse overlays the default values first... then the bird overlays
the horse values... then the custom values come into play. There's
only a single 'skin' field that results, and objects of any type -
horse, bird, animal, or pegasus - can use it directly without any
casting required.

But! What's really neat about it, is this inheritance is really, at
its base, a shortcut for specifying all the behavior of an object. If
you want part-but-not-all of the behavior of another class, you can
grab exactly what you want from the text file. The 'flying' behavior
of a bird, the 'skin' of an animal, the 'whinnying' of a horse, and
the 'swimming' of a fish.. without having to inherit ALL the behaviors
and attributes.

I dunno. Take a look. It seems really unique and useful to me. I've
been in the programming field for 19 years now, and have seen quite a
bit come down the pike . I was really excited when I learned Python,
and was eager to put it into practice... until my first timings and
stress tests came back. It took 8x more memory, and was 15x slower
than C. :-O I could have lived with the 15x slower, believe it or
not... but that memory footprint was too painful to deal with. If they
had my memory management scheme, it'd be automatically optimized to
take the least amount of memory possible, without any intervention on
the part of the programmer.

Anyways, I'm languishing for lack of feedback, and if anyone cares to
give it a thorough going over, by downloading the code and look at it
in depth, I'd appreciate it.
 
C

Calum

It looks really fun.

It looks appropriate for open-world type programming like MUDs and GUIs,
where you'd like to tag attributes to an existing object.

The problem is performance isn't going to be great, because looking up
the location of an attribute at run-time is much slower than doing it at
compile-time. People who write in C do so presumably because they need
good performance.

Using enums looks sensible, slight drawback is that binary compatibility
is much more difficult since two modules may have a name clash.

How do you manage types? I mean, what stops you using an int as a
function, or a double as a string? I suppose it is C, so the programmer
is supposed to wait for it to crash to find the errors... ;-) Suggest
adding a type field.

You mention in another post your memory footprint of 190 bytes per
object. Well, why not share hash-tables between objects of the same
type, like a vtable, with an overhead of just 4 bytes per object (plus
data stored in object). Your hash table could contain just function
pointers, and you would have get_X and set_X methods to alter data,
stored in a traditional C style in the struct? (Like COM?) This would
require a redesign. You could hack inheritance by putting your base
class as the first member of your derived class, but multiple
inheritance would require much more work, you need to be able to reroute
messages in your hash table by storing another offset.

If you share hash tables between objects, you can afford to make them
bigger -> fewer hash probes -> faster.

Another idea which you might already be doing. Persistent storage of
objects. Also, what about automatic memory management, garbage
collection, string management? You'd have all the facilities of a
scripting language in C! Maybe you could just use the C API from Parrot
or something?

Calum
 
K

Kamilche

Calum said:
The problem is performance isn't going to be great, because looking up
the location of an attribute at run-time is much slower than doing it at
compile-time.

Oh yeah, it's not as fast as using fields directly. But you know, hash
tables have close to O(1) performance, and I can read 5 million stats
a second using a 1.2 GHz machine. So performance isn't a problem.
Using enums looks sensible, slight drawback is that binary compatibility
is much more difficult since two modules may have a name clash.

I don't see how using an enum could result in a name clash. This code
is polymorphic, both in event processing and in attribute lookup - a
'draw' event for a circle uses a different function than a 'draw'
event called for a square.

How do you manage types? I mean, what stops you using an int as a
function, or a double as a string?

OK, look at the 'stats.txt' file. The programmer is expected to
include the stat in the proper section - if it's a string, put it in
the 'string' section, etc. So most of the burden is removed from the
programmer, but being C, nothing stops them from intentionally
manipulating a string as an int.
You mention in another post your memory footprint of 190 bytes per
object. Well, why not share hash-tables between objects of the same
type, like a vtable, with an overhead of just 4 bytes per object (plus
data stored in object).
If you share hash tables between objects, you can afford to make them
bigger -> fewer hash probes -> faster.

Uh, no. I guess you didn't look at the code in depth. Look at the
'master' object vs. the 'instance' object - you'll see that aggressive
optimization is already being performed. Let's say you have a million
apples in memory, and decide to add a new stat 'StemLength'. How many
bytes of memory does it take? 4 bytes total. This is because you're
adding it to the 'master' object, not all million apples. Because it's
in the master object, it's automatically available to all the instance
objects. When an instance object needs to override stem length, at
that point, it recreates that stat in its own storage bag.
Another idea which you might already be doing. Persistent storage of
objects.

Yeah, I'm doing that in my own code, but I stripped it out for this
simple example. There's no garbage collection, but there is a custom
memory manager to track leaks for you. It appears you didn't actually
look at the code. Thanks anyway, but I was hoping for feedback from
someone who was interested enough to actually look at what was in
there, instead of making guesses as to how it was implemented.
 
X

Xenos

Kamilche said:
Calum <[email protected]> wrote in message
Oh yeah, it's not as fast as using fields directly. But you know, hash
tables have close to O(1) performance, and I can read 5 million stats
a second using a 1.2 GHz machine. So performance isn't a problem.

Big-O notation is only an indication of time relative to the number of
elements. Saying something is O(1), or constant time, does not imply that
it is fast (however you define fast). It only says that it does not change
as the number of elements increases. A function could take 10 years to
execute, but if it is the same time regardless if you have 1 element or a
million, then the algorithm the function uses is still O(1). Big-O just
defined an upper bound to the time, relative to the data.

DrX.
 
M

Mike

....
Here's a typical example used in C++ texts:

Animal object - has skin color, implements 'walk' and 'talk.'
Horse object - inherits 'animal', overrides 'talk' with 'neigh.'
Bird object - inherits 'animal', adds 'fly', overrides 'talk' with
'chirp.'
Pegasus object - inherits from both horse and bird.

In C++, this multiple inheritance results in the 'skin color' stat
being inherited from both horse and bird. This makes the structure
weighty, and adds such a complication to programming, that most
programmers just avoid multiple inheritance.

Using the method I devised, the fields aren't duplicated, they're
'overlaid', and the last one in gets to set the default value. If you
create a 'pegasus' and specify 'inherits: horse' and 'inherits: bird',
the horse overlays the default values first... then the bird overlays
the horse values... then the custom values come into play. There's
only a single 'skin' field that results, and objects of any type -
horse, bird, animal, or pegasus - can use it directly without any
casting required.

Does tis mean that Pegasus 'chirp's?
 
C

Calum

Kamilche said:
Oh yeah, it's not as fast as using fields directly. But you know, hash
tables have close to O(1) performance, and I can read 5 million stats
a second using a 1.2 GHz machine. So performance isn't a problem.

It's all relative. That's why so many use scripting languages nowadays...
I don't see how using an enum could result in a name clash. This code
is polymorphic, both in event processing and in attribute lookup - a
'draw' event for a circle uses a different function than a 'draw'
event called for a square.

Okay, if in my enum, Square=25 since it was 26th in the table when I
compile it. What if Fred compiles his code, and Square=14. If you
tried pooling my and Fred's code, and you only had the binaries, would
there not be a problem with that?

An alternative method is COMDAT/string pooling, so the same char* -> the
same string. Just an irrelevant diversion, sorry.
OK, look at the 'stats.txt' file. The programmer is expected to
include the stat in the proper section - if it's a string, put it in
the 'string' section, etc. So most of the burden is removed from the
programmer, but being C, nothing stops them from intentionally
manipulating a string as an int.

I see you already have a Type field in your hashtable, making my comment
superfluous. If you inherit, how do you ensure that type is consistent?
Perhaps you could return the type field with GetStat()? What about adding

GetIntStat()
GetStringStat()
GetFnStat()
GetHashStat()

which could assert the type of the field, for those interested in safety.
Uh, no. I guess you didn't look at the code in depth. Look at the
'master' object vs. the 'instance' object - you'll see that aggressive
optimization is already being performed. Let's say you have a million
apples in memory, and decide to add a new stat 'StemLength'. How many
bytes of memory does it take? 4 bytes total. This is because you're
adding it to the 'master' object, not all million apples. Because it's
in the master object, it's automatically available to all the instance
objects. When an instance object needs to override stem length, at
that point, it recreates that stat in its own storage bag.

Quoting from your source code main.c:52-55:

"Each object that doesn't override any stats in the master, takes up
approximately 190 bytes. Therefore, 1,000,000 objects will fit in 181
megs of memory. As you modify and add stats to objects, this number will
increase."

So, how exactly does 190 bytes tally with 4 bytes?

I ran your code to creating 500,000 objects. It took about 5 minutes to
free them, I think there may be a problem here? Looking only at core
size, it required 108MB optimized, i.e. 200bytes per object as you said.
Contrast this with

struct Apple
{
HASHTABLE vtable; // A pointer, shared between all Apple objects
int weight, colour;
};

Apple *apples = malloc(sizeof(Apple) * 500000);
....

vtable would implement set_weight(), get_weight(), set_colour(),
get_colour(), draw(). If vtable were shared, you would only require 12
bytes per Apple, 6MB for 500,000. I only mention it since you stressed
the importance of memory efficiency. Ignore it if you will.
Yeah, I'm doing that in my own code, but I stripped it out for this
simple example. There's no garbage collection, but there is a custom
memory manager to track leaks for you. It appears you didn't actually
look at the code. Thanks anyway, but I was hoping for feedback from
someone who was interested enough to actually look at what was in
there, instead of making guesses as to how it was implemented.

Well I'm sorry for not going through it line by line, but I've had a
look, see PS. Interestingly (perhaps), since you use VC6, you could
look at CCmdTarget, which chains together hash tables, for the purposes
of routing Windows messages (enumerated, takes 2 ints, returns an int).
Similar?

Regards,

Calum


P.S. You need a second mem_free(tables) in test_start().
 
T

Tim Cambrant

Kamilche said:
I have looked at many object-oriented programming frameworks out there
for C. Though the ideas presented are intriguing, and I've used some
of them in my own work, they all suffered some drawback or another.

I have created a new method of doing OOP with C, something that fits
my needs particularly well. I haven't encountered anything else like
it, so I'm tossing it out to the Internet for comment.

http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=6820&lng
WId=3

Here's a bit more info about it, in case you're interested:

This framework allows for object oriented processing in C.
Instead of structs with hard-coded fields, every object is a
hash table that can contain an infinite number of name/value pairs,
called 'stats' from here on out. So - instead of this:

struct PERSON_T
{
char *name; // filled with 'Harry
int age; // filled with '17'
}

you'd have this instead:

HASHTABLE (list of name/value pairs)
StatName StatValue
-------------- --------------
name Harry
age 17

(The stat names are actually stored as enums, for maximum lookup
speed.)

Each type of object that can exist, such a 'square' or 'circle,'
must have an entry in the master table, that sets the 'default field
values' for that type of object. This entry is called a 'master item.'
Each master item in the master table is itself a hash table, so the
master table can be considered a 'table of tables.'

When accessing the fields of an object, use GetStat or SetStat.
They work in the following manner:

* GetStat - Retrieves the value from the object, if that
name/value pair exists.
If it doesn't, retrieves the value from the
master table for that object type.

* SetStat - Sets the value in the object only -
never modifies the master table!

This is an 'auto-optimizing' memory scheme. Only the differences
between the object, and the 'master object' for that type, are stored.
This allows you to create millions of items in memory, with thousands
of stats each, in a very small amount of space.
If the stat exists in the object, it is pulled from the object.
If it doesn't, it assumes 'the default value' from the master is
good enough, and uses that instead.

Constructing objects is easy - just call the Create routine with the
name of the master object, plus all the stats you want to override.

In addition to storing the default stats, the master table stores
function pointers to event handlers for that object.

Look at the following master item example:

CIRCLE
StatName StatValue
---------- --------------
Type 200
Name CIRCLE
Inherits SHAPE
Radius 1
OnDraw circle_draw
OnSetRadius circle_setradius

This master item describes a new object type called 'CIRCLE', that
has a type of 200, a name of CIRCLE, and a default radius of 1.
When events are sent to the object, it looks for the corresponding
'OnEvent' stat for the specified event, and calls the function
through that function pointer. For instance - when the 'draw'
command is sent to it, it pulls up the 'OnDraw' stat, and picks out
the function pointer. Then, it executes the 'circle_draw' routine
through that function pointer. When a 'setradius' command is sent
to it, it executes the 'circle_setradius' routine.

It also illustrates the 'inheritance' feature. Inheritance comes into
play only when loading the master file. When an 'Inherits' stat is
encountered, the master loading routine pulls up the specified master
item, and pulls all ITS stats out and adds it to the current master
item.
In this case, the master table entry for 'SHAPE' looks like this:

SHAPE
StatName StatValue
---------- --------------
Type 100
Name SHAPE
Inherits BASE
OnMove shape_move
OnSetName shape_setname

All stats except type and name will be added to the CIRCLE master
item.
Though not illustrated, you can inherit from multiple base types.
Simply specify multiple INHERITS stats when loading the master file.
The stats will be pulled in order, with newer entries overlaying older
entries, so be careful to specify the inheritance loading order
accordingly!

After all the inheritance is performed, the CIRCLE item actually looks
like this in memory:

CIRCLE
StatName StatValue
---------- --------------
Type 200
Name CIRCLE
Radius 1
OnDraw circle_draw
OnSetRadius circle_setradius
OnMove shape_move
OnSetName shape_setname

As you can see, 'draw' and 'setradius' are specific to the circle
master item. 'move' and 'setname' are inherited from the 'shape'
master item. (The 'inherits' stat is missing entirely, as that is
only used when loading the master table into memory.)

All method calls are done via 'Do'. Do takes the item,
the event, and any additional arguments. It looks up which stat is
associated with the event, and retrieves that function pointer.
If the function pointer exists, it executes it with the supplied
arguments. If it doesn't exist, it returns an ERR_UNHANDLED_EVENT
error.

All possible events must be listed in the 'events.txt' file, and
all possible stats must be listed in the 'stats.txt' file.
All the errors are listed in the 'errors.txt' file.
A stringizing macro STRINGIZE is used to turn these into the
appropriate enum and arrays in code.

That's about it. I've stripped this down from my own code to its
bare essentials. In my code, I load the master table from disk, and
so should you! This makes it easy to modify functionality of objects
without cutting any code. I also have specialized 'load from disk' and
'save to disk' handling for the objects, not implemented here.
As you might guess, it's only necessary to store the stats from the
object hash table, not the master item hash table.
The master table should only ever be modified 'by hand'.

I hope you find some of these concepts useful in your own work!
If you do any enhancements, drop me a line.

I've been reading this thread now and I think it sounds good for now, but
since I don't know much about programming, I think a few example-programs
written in your code would really help convince me and other people like me.
We who don't know much about the benefits of enum's etc, rely more on the
fact that you can actually do something with the language.

Might I suggest that you, after testing and such of course, release a few
programs you've written with this code? I think this would convince more
people that it is a good implementation you've done.
 
K

Kamilche

Calum said:
Okay, if in my enum, Square=25 since it was 26th in the table when I
compile it. What if Fred compiles his code, and Square=14. If you
tried pooling my and Fred's code, and you only had the binaries, would
there not be a problem with that?

Ooh, well, I see where you're going. This is intended for use by a
single program - it's not a general purpose library, you're right.
What about adding

GetIntStat()
GetStringStat()
GetFnStat()
GetHashStat()

which could assert the type of the field, for those interested in safety.

Yep, that'd work. I've got it in my own code, but I stripped it out
for this example. I really trimmed it to death, in order to present
its 'basic structure.'
Contrast this with

struct Apple
{
HASHTABLE vtable; // A pointer, shared between all Apple objects
int weight, colour;
};

Apple *apples = malloc(sizeof(Apple) * 500000);
...

vtable would implement set_weight(), get_weight(), set_colour(),
get_colour(), draw(). If vtable were shared, you would only require 12
bytes per Apple, 6MB for 500,000.

OK, I see you're storing 'weight' and 'color' for every apple. What if
you had a more complete description, with say, 100 descriptive fields?
You begin to see the problem. Why store all those fields that are the
same as the 'master' item? I don't - I store them only if they differ
from the master item. So for only 190 bytes overhead, you get
essentially unlimited record size for free, assuming most apples
differ from the base by only a small amount.

Essentially, this method does for functions AND fields, what C++ does
for functions only.
P.S. You need a second mem_free(tables) in test_start().

Yep - I saw that, thanks though. I'm modifying it right now to have
constructors and destructors, and will post it with that memory leak
fixed when I'm satisfied with it.

Thanks for the more detailed feedback!

--Kamilche
 
K

Kamilche

Tim Cambrant said:
Might I suggest that you, after testing and such of course, release a few
programs you've written with this code? I think this would convince more
people that it is a good implementation you've done.

I'm not trying to peddle anything. The next version I post will have a
few more bells and whistles, including a timing class, a test class,
and constructors/destructors, maybe that will serve your needs. I
find when I post big chunks o code, they're poorly received, because
not everyone has time to pick through an entire program, just to look
at one interesting new concept. I'm trying to get feedback on the
concept itself, and maybe a person who's interested enough in it to
use it, or has suggestions or enhancements, or something.

--Kamilche
 
C

Calum

Kamilche said:
OK, I see you're storing 'weight' and 'color' for every apple. What if
you had a more complete description, with say, 100 descriptive fields?
You begin to see the problem. Why store all those fields that are the
same as the 'master' item? I don't - I store them only if they differ
from the master item. So for only 190 bytes overhead, you get
essentially unlimited record size for free, assuming most apples
differ from the base by only a small amount.

Essentially, this method does for functions AND fields, what C++ does
for functions only.

There are ways around this, functions can return values after all,
thereby behaving like constant fields. Classes can also inherit
constants from their base classes. And then you have static data. For
example

class Thing
{
public:
virtual const char *get_description() const =0;
virtual void set_description(const char *)
{ throw "You can't set this!"; }
};

class Apple : public Thing
{
public:
const char *get_description() const { return "I am an apple"; }
};

So Apple has the same get_description() field for all Apples, and
therefore does not require object storage - just like your system. The
problem is, if we change our mind, and *really* want to change the
description - we can't. We'd have to go out of our way to implement that.

class GrannySmith : public Apple
{
const char *description;
public:
GrannySmith() : description("I am a granny smith") { }
void set_description(const char* x) { description = x; }
const char *get_description() const { return description; }
};


What you have is much more flexible. That's the thing about
[traditional] object orientation, it rigidly defines the contents of
your class, great for compilation and speed, weak on flexibility. But
then it can also be argued that strong types make programs more
structured and easier to read and debug. Depends on what you're writing
I suppose.

Calum
 
W

Wolfgang Riedel

Kamilche said:
I have looked at many object-oriented programming frameworks out there
for C. Though the ideas presented are intriguing, and I've used some
of them in my own work, they all suffered some drawback or another.

I have created a new method of doing OOP with C, something that fits
my needs particularly well. I haven't encountered anything else like
it, so I'm tossing it out to the Internet for comment.

http://www.planet-source-code.com/vb/scripts/ShowCode.asp?txtCodeId=6820&lngWId=3

Here's a bit more info about it, in case you're interested:

This framework allows for object oriented processing in C.
Instead of structs with hard-coded fields, every object is a
hash table that can contain an infinite number of name/value pairs,
called 'stats' from here on out. So - instead of this:

struct PERSON_T
{
char *name; // filled with 'Harry
int age; // filled with '17'
}

you'd have this instead:

HASHTABLE (list of name/value pairs)
StatName StatValue
-------------- --------------
name Harry
age 17

(The stat names are actually stored as enums, for maximum lookup
speed.)

Each type of object that can exist, such a 'square' or 'circle,'
must have an entry in the master table, that sets the 'default field
values' for that type of object. This entry is called a 'master item.'
Each master item in the master table is itself a hash table, so the
master table can be considered a 'table of tables.'

When accessing the fields of an object, use GetStat or SetStat.
They work in the following manner:

* GetStat - Retrieves the value from the object, if that
name/value pair exists.
If it doesn't, retrieves the value from the
master table for that object type.

* SetStat - Sets the value in the object only -
never modifies the master table!

This is an 'auto-optimizing' memory scheme. Only the differences
between the object, and the 'master object' for that type, are stored.
This allows you to create millions of items in memory, with thousands
of stats each, in a very small amount of space.
If the stat exists in the object, it is pulled from the object.
If it doesn't, it assumes 'the default value' from the master is
good enough, and uses that instead.

Constructing objects is easy - just call the Create routine with the
name of the master object, plus all the stats you want to override.

In addition to storing the default stats, the master table stores
function pointers to event handlers for that object.

Look at the following master item example:

CIRCLE
StatName StatValue
---------- --------------
Type 200
Name CIRCLE
Inherits SHAPE
Radius 1
OnDraw circle_draw
OnSetRadius circle_setradius

This master item describes a new object type called 'CIRCLE', that
has a type of 200, a name of CIRCLE, and a default radius of 1.
When events are sent to the object, it looks for the corresponding
'OnEvent' stat for the specified event, and calls the function
through that function pointer. For instance - when the 'draw'
command is sent to it, it pulls up the 'OnDraw' stat, and picks out
the function pointer. Then, it executes the 'circle_draw' routine
through that function pointer. When a 'setradius' command is sent
to it, it executes the 'circle_setradius' routine.

It also illustrates the 'inheritance' feature. Inheritance comes into
play only when loading the master file. When an 'Inherits' stat is
encountered, the master loading routine pulls up the specified master
item, and pulls all ITS stats out and adds it to the current master
item.
In this case, the master table entry for 'SHAPE' looks like this:

SHAPE
StatName StatValue
---------- --------------
Type 100
Name SHAPE
Inherits BASE
OnMove shape_move
OnSetName shape_setname

All stats except type and name will be added to the CIRCLE master
item.
Though not illustrated, you can inherit from multiple base types.
Simply specify multiple INHERITS stats when loading the master file.
The stats will be pulled in order, with newer entries overlaying older
entries, so be careful to specify the inheritance loading order
accordingly!

After all the inheritance is performed, the CIRCLE item actually looks
like this in memory:

CIRCLE
StatName StatValue
---------- --------------
Type 200
Name CIRCLE
Radius 1
OnDraw circle_draw
OnSetRadius circle_setradius
OnMove shape_move
OnSetName shape_setname

As you can see, 'draw' and 'setradius' are specific to the circle
master item. 'move' and 'setname' are inherited from the 'shape'
master item. (The 'inherits' stat is missing entirely, as that is
only used when loading the master table into memory.)

All method calls are done via 'Do'. Do takes the item,
the event, and any additional arguments. It looks up which stat is
associated with the event, and retrieves that function pointer.
If the function pointer exists, it executes it with the supplied
arguments. If it doesn't exist, it returns an ERR_UNHANDLED_EVENT
error.

All possible events must be listed in the 'events.txt' file, and
all possible stats must be listed in the 'stats.txt' file.
All the errors are listed in the 'errors.txt' file.
A stringizing macro STRINGIZE is used to turn these into the
appropriate enum and arrays in code.

That's about it. I've stripped this down from my own code to its
bare essentials. In my code, I load the master table from disk, and
so should you! This makes it easy to modify functionality of objects
without cutting any code. I also have specialized 'load from disk' and
'save to disk' handling for the objects, not implemented here.
As you might guess, it's only necessary to store the stats from the
object hash table, not the master item hash table.
The master table should only ever be modified 'by hand'.

I hope you find some of these concepts useful in your own work!
If you do any enhancements, drop me a line.

--Kamilche, (e-mail address removed)

sounds fun indeed, but as long as you can't give me an ~, I'll stay with an OOL,
if I want OOP.
(Might be very different, if I needed a progamming/execution env. where's no
C++ -compiler/runtime)
(btw., you know http://ldeniau.home.cern.ch/ldeniau/html/oopc/oopc.html ?)

Greetings
Wolfgang
 
P

Programmer Dude

Kamilche wrote:

I haven't time to examine the code, but you said you were starving
for input, so will you accept some based on your posts?
I haven't encountered anything else like it,...

As others have mentioned, this is actually similar to a number of
other language "OOP" implementations. It is very similar to Perl
and JavaScript and Lisp (which, if anything, suggests you're keeping
good company! :).
Instead of structs with hard-coded fields, every object is a
hash table that can contain an infinite number of name/value
pairs, called 'stats' from here on out.

A little slower, but these days probably not at all an issue.
(The stat names are actually stored as enums, for maximum lookup
speed.)

IF IT WERE ME, I might consider a system that binds names to integer
values at runtime. This may be more flexible and removes a potential
source of problems with synchronization. It might also allow you to
completely forego the enum table and the need to recompile should you
add a new stat name: just load a table of stat names at startup.
* GetStat - Retrieves the value from the object, if that
name/value pair exists.
If it doesn't, retrieves the value from the
master table for that object type.

(Idle question: what if it's not in the master table? (Feel free
to flame me for not reading the code! :))
Though not illustrated, you can inherit from multiple base types.
Simply specify multiple INHERITS stats when loading the master file.
The stats will be pulled in order, with newer entries overlaying
older entries, so be careful to specify the inheritance loading
order accordingly!

Suppose you want to NOT overwrite a particular stat and no loading
order allows the configuration you want? Not an issue? If it is,
perhaps a keyword that specifies override or not?
If the function pointer exists, it executes it with the supplied
arguments. If it doesn't exist, it returns an ERR_UNHANDLED_EVENT
error.

[grin] Just like the Smalltalk R/T error: Method Not Found!

I can't tell: are events different from method calls? My way of
looking at it, "events" are asynchronous things: user clicked a
button; system sent a signal. Methods are invoked as part of the
normal flow of code.
All possible events must be listed in the 'events.txt' file, and
all possible stats must be listed in the 'stats.txt' file.
All the errors are listed in the 'errors.txt' file.
A stringizing macro STRINGIZE is used to turn these into the
appropriate enum and arrays in code.

So,... you're doing this at *compile* time, not run time, yes?
Changes require a recompile? Might be nice if program startup
allowed for table and system changes. Less work for you. One
could conceivably even re-load a new configuration WHILE the
system was running.
I hope you find some of these concepts useful in your own work!

I'm afraid I wouldn't use C for something like this. No point in
jumping through hoops when other languages do it for you right out
of the box. I know you're concerned about speed; I'd probably opt
for C++, but YMMV.
 
P

Programmer Dude

Kamilche said:
1) The memory handling really looks unique to me. [...]
By storing only the differences between the base object and the
instance, my method of storage results in vast CPU and storage
savings.

Yes, that IS a cool idea! And possibly quite unique.
Here's a typical example used in C++ texts:

Animal object - has skin color, implements 'walk' and 'talk.'
Horse object - inherits 'animal', overrides 'talk' with 'neigh.'
Bird object - inherits 'animal', adds 'fly', overrides 'talk'
with 'chirp.'
Pegasus object - inherits from both horse and bird.

In C++, this multiple inheritance results in the 'skin color' stat
being inherited from both horse and bird. This makes the structure
weighty, and adds such a complication to programming, that most
programmers just avoid multiple inheritance.

I would consider this a broken design. A Pegasus is NOT a bird,
and ONLY shares a flying characteristic. I would probably inherit
from horse, but I would never from bird.
Using the method I devised, the fields aren't duplicated, they're
'overlaid', and the last one in gets to set the default value. If
you create a 'pegasus' and specify 'inherits: horse' and 'inherits:
bird', the horse overlays the default values first... then the bird
overlays the horse values...

Giving you a flying horse that chirps and eats birdseed???? (-:
If you want part-but-not-all of the behavior of another class,
you can grab exactly what you want from the text file.

Then it really isn't inheritance, but something else (and more
appropriate for your use). It almost sounds like a "dictionary"
of attributes (stats) where you can grab whatever you need. The
initial organization of stats into 'horse' or 'bird' seems to be
just that: an organization of sorts.

Would it be of any value to *just* provide the stats without any
"object" organization? You could build some wild runtime objects
that way....
Anyways, I'm languishing for lack of feedback,...

I put off answering hoping I'd get some time to go over the code,
but that's not going to happen anytime soon, so....
 
K

Kamilche

Programmer Dude said:
IF IT WERE ME, I might consider a system that binds names to integer
values at runtime.

Yeah, that's a possibility. I have it 'somewhat' automatic now, by
using the stringizing macro... but a list loaded at runtime would have
some additional advantages.
(Idle question: what if it's not in the master table? (Feel free
to flame me for not reading the code! :))

It doesn't allow you to create it, and returns NULL instead of a
pointer to an object.

Suppose you want to NOT overwrite a particular stat and no loading
order allows the configuration you want? Not an issue? If it is,
perhaps a keyword that specifies override or not?

You know, for my use that's not really an issue. I 'know' that the
inherits clauses are just a way of avoiding typing out the entire
function list... if I wanted to, I could manually enter the entire
function hierarchy in the master file, and come up with a partial
inheritance. And I imagine I'll do that quite a bit.

The scenario comes into play for issues like this: Let's say you have
a pair of pants with 2 functions - 'wear', that puts them on, and
'open', which results in an error message saying 'you can't open your
pants!' Now, let's say you have a container, which can be opened, such
as a backpack. If you wanted a pair of 'pants of holding,' which can
be both worn and opened, you could specify both functions, thus
specifically NOT getting the "you can't open the pants" function that
normally comes when you send an 'open' event to the pants object. This
is not something you'd probably want to do, just a contrived example
of how to avoid inheriting a function you don't want. In the code I'm
writing, any unhandled events are given a generic 'you can't do x to
y' message, and I only write functions for legitimate (handled)
events. If I avoid negative "you-can't-do-that functions", inheritance
will work the way I expect it to without having to massage the master
file.
I can't tell: are events different from method calls? My way of
looking at it, "events" are asynchronous things: user clicked a
button; system sent a signal. Methods are invoked as part of the
normal flow of code.

You know, there is a difference, but it's subtle. An event is
something that is requested by a user or bit of code... a method call
is something that can result when processing an event. As an example,
the 'Look' event for an apple runs the generic 'look at item'
function, and the 'Look' event for a Medusa runs the 'kill the person
for looking' function. One event, different functions. There's not
always a one to one correspondence between event and function.
I'm afraid I wouldn't use C for something like this. No point in
jumping through hoops when other languages do it for you right out
of the box. I know you're concerned about speed; I'd probably opt
for C++, but YMMV.

Yeah, I see your point. If I didn't have to have a million objects in
memory, I'd probably use Python, myself - it seems almost tailor made
to this situation. But I don't want to have to vastly limit my online
world and create 'shards' to get around the memory issues... I'd like
to have as many people online at once as I possibly can, at least
2000, before I consider a multiple server scenario. So - C is
appealing to me, you just can't beat it for execution size and speed!

Thanks for evaluating it! :)

--Kamilche
 
P

Programmer Dude

Kamilche said:
Yeah, that's a possibility. I have it 'somewhat' automatic now, by
using the stringizing macro... but a list loaded at runtime would
have some additional advantages.

Maybe something for v2. (-:

(I stressed "if it were me", because I really like programs that
are driven by data. A personal preference, if you will. But I do
see some definite advantages.)

You know, for my use that's not really an issue.

Having seen your thoroughness previously, I figured if the capability
wasn't there, you didn't need it!
Let's say you have a pair of pants with 2 functions - 'wear', that
puts them on, and 'open', which results in an error message saying
'you can't open your pants!'

I'm not sure I follow. If you can't 'open' pants, why do you have
the function?
Now, let's say you have a container, which can be opened, such
as a backpack. If you wanted a pair of 'pants of holding,' which
can be both worn and opened, you could specify both functions, thus
specifically NOT getting the "you can't open the pants" function
that normally comes when you send an 'open' event to the pants
object.

I'm lost. What happened to that backpack??

I did go grab your code hoping the .txt files would clear up my
confusion, but not so much. No pants or backpacks. :-(
In the code I'm writing, any unhandled events are given a generic
'you can't do x to y' message,

A fine idea.
...and I only write functions for legitimate (handled) events.

Of course. Ah. This means you can't define a new event JUST thru
the text files, doesn't it. Be neat if there was a way you could!
You know, there is a difference, but it's subtle. An event is
something that is requested by a user or bit of code... a method
call is something that can result when processing an event.

Heh! Doesn't sound all THAT subtle!! (-:
As an example, the 'Look' event for an apple runs the generic
'look at item' function, and the 'Look' event for a Medusa runs
the 'kill the person for looking' function.

I think I see. It appears each "class" registers its methods in
the _initialize() method? The event handler looks to see if a
given class has registered a method and, if so, calls it?

So, what exactly happens if I look at an apple and than at Medusa?
(I mean, what happens in the respective _look() methods? How does
the system know Medusa kills me, but the apple--what--just "returns"
red?)
There's not always a one to one correspondence between event and
function.

Are you saying an event may not find a method for a given class, or
is there more to the "not one to one"?

Yeah, I see your point. If I didn't have to have a million objects
in memory, I'd probably use Python, myself - it seems almost tailor
made to this situation.

Could be, I'm not Python-aware (yet--someday maybe). C++ would give
you the speed and flexibility of C, but might also provide a helping
hand with some of the OOP-ish aspects you're doing by hand now.

[shrug] Hard to say.
I'd like to have as many people online at once as I possibly can,
at least 2000,...

WOW! That'd be some game!!
 
K

Kamilche

Programmer Dude said:
Having seen your thoroughness previously, I figured if the capability
wasn't there, you didn't need it!

Oh, it's there, and I know I'll use it... just not that often
probably.
I'm not sure I follow. If you can't 'open' pants, why do you have
the function?

You're right, if this were a real scenario, you would simply not code
up anything at all for pants - not a 'negative' function like this. I
had a hard time coming up with an example, what can I say.
Of course. Ah. This means you can't define a new event JUST thru
the text files, doesn't it. Be neat if there was a way you could!

You can think of an event as a request that generates some response.
The requester has no control over how the event is handled, or what
function is called. Each master object in memory has a list of events,
each of which is filled with the function it calls to handle that
event. So - events are the 'slots', and methods are the things filling
those slots.

To an extent, you CAN define a new event for an object via text files.
If the event already exists, but the object simply isn't handling it -
'eat' for a tree, for instance - you just tack that into the master
file, along with the name of the function you want to execute when
that event is received. If the function is unique, and doesn't already
exist, you'd end up cutting code, but most of the time, the objects
react in the same old ways - get an item, drop an item, destroy an
item, pull a lever, kill the player, etc. So - once you've got the
core functions done, adding responses to events becomes a simple
text-file manipulation. A lot of the advantages of an interpreted
language, without the overhead of run-time parsing. :)

I think I see. It appears each "class" registers its methods in
the _initialize() method? The event handler looks to see if a
given class has registered a method and, if so, calls it?

The xxx_initialize function for each class registers a list of all
functions it has available. It's the master file that 'hooks' those
functions to actual events, by object.

Do(apple, Look) results in the generic 'object_look' function being
executed, which looks up the description of the item, and sends it
back to the player. Do(medusa, Look) results in the specific
'medusa_look' function, which kills the player.
Are you saying an event may not find a method for a given class?

I mean events are not methods. If an event field is blank for a master
object, that means it doesn't handle that event. If it's non blank, it
pulls out the function and executes it.
Could be, I'm not Python-aware (yet--someday maybe). C++ would give
you the speed and flexibility of C, but might also provide a helping
hand with some of the OOP-ish aspects you're doing by hand now.

It could... I looked at C++, and started redoing my codebase in it,
but ended up walking away from it. I really don't like C++ at all.
I've done OOP with VB and other languages, so I'm familiar with the
concepts... I just don't like how C++ implements those concepts. It
adds too much complexity, yet when push comes to shove, doesn't add
enough functionality to make it worthwhile, for me at least. I was
appalled when I learned it didn't handle events, like VB... didn't
have built-in hash tables... learned that code for templates had to
all be stuffed in a header file... methods sometimes got called BEFORE
the constructor ran... iterators don't let you iterate and delete at
the same time... more. After tackling the huge learning curve, I
stumbled over too many bogus things. In comparison, Python and VB were
a joy to use, each in their own way. There's no joy in C++ land. :p
:-D
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top