Pages: « 1 2
  Print  
Author Topic: Accessing with types and passing by strong reference  (Read 27721 times)
Offline (Male) Josh @ Dreamland
Reply #15 Posted on: July 25, 2014, 11:10:19 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
Pointers are just sparse integers. Yes, it's easy to obtain the next sprite given the current one, because usually you just add one. Well, let's say we handed around raw pointers. Now you can usually obtain the next sprite given the current one by adding sizeof spritestruct. The only difference is that you'll get an access violation and kill your program if you're wrong in the pointer case. As I stated, I'm fine with having a typedef of size_t for each resource kind. This would be a hint to the compiler that it is not to be used in arithmetic. It could also be used to allow object-oriented programming around that type; eg, replace [snip=edl]spr_whatever.draw(0, x, y)[/snip] with [snip=edl]draw_sprite(spr_whatever, 0, x, y)[/snip]. There only difference is that this "pointer" is small, dense, and managed by us.
Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Male) Rusky
Reply #16 Posted on: July 25, 2014, 11:16:22 pm

Resident Troll
Joined: Feb 2008
Posts: 954
MSN Messenger - rpjohnst@gmail.com
View Profile WWW Email
I'm not talking about the actual content bits, in that sense it doesn't matter if you use ints or Something*s. I'm talking about the type flag in variant- rather than just typedef'ing size_t (which the compiler just ignores for type checking purposes without so much as a warning), associate some actual type information with the different resource spaces.

There's two ways to do that- at compile time, with a wrapper struct rather than a typedef; and as another case of variant, which would be a drop-in replacement for GM's system but with the ability to catch more errors.
Logged
Offline (Unknown gender) TheExDeus
Reply #17 Posted on: July 26, 2014, 08:43:33 am

Developer
Joined: Apr 2008
Posts: 1860

View Profile
Quote
Pointers are just sparse integers. Yes, it's easy to obtain the next sprite given the current one, because usually you just add one. Well, let's say we handed around raw pointers. Now you can usually obtain the next sprite given the current one by adding sizeof spritestruct. The only difference is that you'll get an access violation and kill your program if you're wrong in the pointer case. As I stated, I'm fine with having a typedef of size_t for each resource kind. This would be a hint to the compiler that it is not to be used in arithmetic. It could also be used to allow object-oriented programming around that type; eg, replace spr_whatever.draw(0, x, y) with draw_sprite(spr_whatever, 0, x, y). There only difference is that this "pointer" is small, dense, and managed by us.
I don't want "spr_whatever.draw()" or any other function like that at all. I don't want spr_whatever to be a class with members. I want that when I pass spr_whatever (with ID = 2, for example) to a draw_background(), I would know that it's incorrect, and that I am not in fact drawing background with ID = 2. So I want it to trow an error, as well as allow me create more generic functions. Like draw_image() which would take both sprite and background. Or replace sprite_get_texture, background_get_texture and surface_get_texture with one function - get_texture(). Or even go one step further and allow draw_primitive_texture() to take sprites, backgrounds and surfaces directly, without specific query for the texture. So I want to be able to figure out what the hell I am passing to the functions, instead of just an "int".
So I also recommended the typedef for this, but how would casting work when using default variable types? Like this this code:
Code: (edl) [Select]
a = 5; //Here variant of type variant with value 5
a = spr_some_guy; //Now "a" need to be typdef'd type "Sprite"

Also, as I mentioned before - doing arithmetic with ID's is retarded. We should not encourage it, and if possible, deny that possibility.
Logged
Offline (Unknown gender) TheExDeus
Reply #18 Posted on: October 03, 2014, 07:09:34 pm

Developer
Joined: Apr 2008
Posts: 1860

View Profile
Quote
Resources, you have hit the limit. There is little reason to ever have spr_some_guy be anything but an integer.
Things like draw_image() would be possible, which would draw a sprite, a background, a texture or a surface depending on what is given. I personally would like that. resource_exist() would also then work like that. Having an identifier without the resource attached would still be possible, and that identifier actually doesn't need to be more than an int. It just has to be an int, which can be differentiated from other int's. That is where my typedef idea comes from. All of this also gives a lot better error handling, as Rusky says. If you have hundreds of resources in a game, then it's actually very likely that any combination of resource types will not error, when they actually should. Like if you have 100 sprites, then draw_sprite(snd_hurt,...) will work if there is less than 100 sounds. So the current error handling cannot catch that at all.

Quote
But then, does spr.exists() really make any sense?
It would, just like sprite_exists(spr) makes sense right now. The problem is how to cast one thing to another. We can make sure that sprite_add() returns sprite_t, and we can make sure sprites in LGM are cast to it, but when do assignments and comparisons (which don't actually make sense), we could get into trouble.

I would really get to the bottom of this now, as I'm still making the GUI extension and as GL fixes come closer to finish, I would like this to be done as well.

edit: There is a discussion about strong type checking here: http://stackoverflow.com/questions/376452/enforce-strong-type-checking-in-c-type-strictness-for-typedefs . There are also some propositions, like the Microsoft variant, or the C++11 enum classes.
« Last Edit: October 03, 2014, 07:16:10 pm by TheExDeus » Logged
Offline (Male) Josh @ Dreamland
Reply #19 Posted on: October 05, 2014, 01:25:16 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
I'm trying to be defensive on one front and am over-defending another. What you are asking me for is type safety, and yes, we can get that. What you're also asking me for in the process is to not use an integer, and that would be fallacy. I stand by my original statement—integers as our reference are opaque. The fact that you have reference collisions does not make the data any less opaque. The point of integers is that they're dense and easy to track—it's trivial to check if a sprite is loaded given its index, and it's harmless to be wrong about whether it's still loaded.

Yes, collisions happen all the fucking time, and when concepts are genuinely confusing—eg, I asked you for a texture index, you passed me a sprite index or a background index—head-scratching behavior can occur.

We don't have the framing right now to really lick this. How about this:  In the interim, why don't you replace int in these functions with sprite_t, background_t, path_t, etc. For now, please just use [snip=edl]typedef int sprite_t;[/snip], etc.



Here's where we run into trouble: you are also asking me for overloads. There's a lot more involved in overloading [snip=edl]widget_set_parent(button_t button, window_t window)[/snip] with [snip=edl]widget_set_parent(window_t window1, window_t window2)[/snip] than you are giving credit for. The compiler can generate an array for the cross-product of all these overloads, if it also generates RTTI metadata for them, but then you still have new problems.

Your first problem is going to be that C++ won't let you overload these integer types. Since they're all integers, you can't create this overload. You can get around this by using a struct instead of a typedef. In doing this, you have created the problem that you can't store these in integers at all, anymore, which may be what you want. If you do this, I recommend having these all inherit a class called reference_index which just stores an integer. This will allow you to roughly implement this alongside the current variant implementation, with a little tweaking.

The second problem is arguably worse. All this sounds great until you realize that you have just moved the problem of overload resolution to runtime.

So let's assume you've overloaded widget_set_parent for a number of different types. You'll have something like this:

Code: (cpp) [Select]
struct window_t: reference_index { /* ... */ }
struct button_t: reference_index { /* ... */ }

void widget_set_parent(window_t window, window_t parent);
void widget_set_parent(button_t button, window_t parent);

Naively, you can give the user methods to "cast" a variant between those. To involve the compiler is to introduce our run-time compile errors. We tell the compiler that it's okay for a user to pass variant to these functions. To enable this to happen, the compiler will generate a run-time type enumeration for variant, like so:
Code: (cpp) [Select]
namespace enigma {
  enum variant_runtime_types {
    VRT_SPRITE,
    VRT_BACKGROUND,
    // ...
    VRT_WINDOW,
    VRT_BUTTON,
    VRT_COMBOBOX,
    // ...
  }
}

It can do this, for example, by querying for members structs of enigma_user which extend reference_index. No problem. It will also generate special methods to fetch these, as needed, and we'll assume
  • that this is done in a way that does not involve modifying var.h when this list changes, so
  • nothing in the engine code relies on that constructor, and so the logic does not need to be known at compile time; and that
  • the actual constructor logic (or most of it) is implemented in the engine's main source where all other user code is generated

I have pasted a sample execution of the idea to Pastebin. It shows the basic stages of this, but does not show a complete variant, nor the cast method. But the cast method looks very similar to the construct method, only it actually checks the value of variant.rtti before returning. In practice, we'll probably replace the template function I showed there with a structure containing those so that we don't generate linker errors in problem scenarios (the missing information will be caught at compile time).

This is fine and dandy, but the C++ compiler can't tell which type a variant should use, because a variant can cast to any of those. Thus, all of those methods are weighted equally to the overload resolving compiler. We have lost compile-time overload checking, which is pretty much nothing new, I suppose. But now the compiler has to deal with this to allow it to happen at all.

To do this, the compiler must first identify functions whose overloads are ambiguous to variant in ISO C++. This is a painful check, but it's doable. The compiler must then generate overloads taking const variant&/const var& for these types. This requires two pieces.

First, we need to declare a place for runtime overload disambiguators:
Code: (cpp) [Select]
// Runtime overload disambiguation
map<tuple<int>, void(*)(variant, variant)> widget_set_parent$_overloads;
static void widget_set_parent$button$window(variant arg0, variant arg1) {
  widget_set_parent((button_t)arg0, (window_t)arg1);
}
static void widget_set_parent$window$window(variant arg0, variant arg1) {
  widget_set_parent((window_t)arg0, (window_t)arg1);
}
static inline void widget_set_parent$_disambiguate(const variant& arg0, const variant& arg1) {
  map<tuple<int>, void(*)(variant, variant)>::iterator it = widget_set_parent$_overloads.find(tuple<int>(arg0.rtti, arg1.rtti));
  if (it != widget_set_parent$_overloads.end())
    return it->second(arg0, arg1);
  show_error("No overload for widget_set_parent(" + rtti_names[arg0.rtti] + ", " + rtti_names[arg1.rtti] + ")");
  return void();
}

The above code assumes we also export the names of these types into an array somewhere, which is also dastardly ugly. It also assumes the existence of a tuple class which is basically a vector that I can construct really easily to save code. :P

Then, we need to populate that map at load time:
Code: (cpp) [Select]
void load_overload_disambiguators() {
  widget_set_parent$_overloads[tuple<int>(VRT_BUTTON, VRT_WINDOW)] = widget_set_parent$button$window;
  widget_set_parent$_overloads[tuple<int>(VRT_WINDOW, VRT_WINDOW)] = widget_set_parent$window$window;
}

And if you want implicit casting, well, you're looking at even more logic generated for the overload disambiguation routine. Coupled with even more metaprogramming-fueled metadata.

But anyway, now you have a very thorough synopsis of how I'd handle it. I imagine if I don't get around to it now, I'll get around to it after you all sit on it for three years and I've long forgotten it's a problem. :P
« Last Edit: October 05, 2014, 01:40:46 pm by Josh @ Dreamland » Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Unknown gender) TheExDeus
Reply #20 Posted on: October 05, 2014, 02:53:38 pm

Developer
Joined: Apr 2008
Posts: 1860

View Profile
That requires a lot of compiler changes and is meant to create different functions for different resources. Which might also be needed in the future, but in my extension, I actually need only to check inside the function what type is given. Like something of this sort would suffice to me:
Code: (c++) [Select]
#include <typeinfo>
        typedef int sprite_t;
        typedef int background_t;
        template<typename T>
int widget_set_background(T image)
{
                if (typeid(image) == sprite_t)) use_sprite();
                else if (typeid(image) == background_t)) use_background();
                else printf("Not a valid type give! Either don't do anything, or just default to sprite or something!");
}
This is sadly not working, because typedef doesn't mean anything to the compiler. typeid(sprite_t) is just an integer. So I cannot differentiate. Strict typedef's for error checking of course would also be useful, as we discussed in this topic. But C++ doesn't allow that either, so just a "typedef" wouldn't solve our problems. But here I don't even want that either. As this will be an extension, then I don't want to overhaul the whole ENIGMA engine to accommodate it. So the only thing I want is the power to figure out the type inside the function.

If nothing else, then how at least I can do this for the custom objects I'm making now? Like supporting sprite_t and background_t is also going to be a monumental change it seems, so what would be the best way to support a "button" and a "window"? If EDL actually allowed C++ pointers, then I would probably just use those. Or actual instances of classes, because I hate pointers.
« Last Edit: October 05, 2014, 02:56:50 pm by TheExDeus » Logged
Offline (Male) Josh @ Dreamland
Reply #21 Posted on: October 05, 2014, 03:36:47 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
Using typeid is dangerous as its behavior is implementation-defined. A compiler could easily tell you that [snip=edl]typeid(T).name()[/snip] is "T". Were that not the case, you could implement the above directly without use of the ENIGMA compiler—you would accept derived_reference_index<T> and then compare typeid(T).name() against typeid(enigma::RTTI::background_t).name(). Nothing you do will make this operation not require surgery on how variant stores its internals, and major surgery on how the engine passes around types.
Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Unknown gender) TheExDeus
Reply #22 Posted on: October 05, 2014, 04:56:36 pm

Developer
Joined: Apr 2008
Posts: 1860

View Profile
I'm screwed then. I can guarantee I won't be able to change the variant or the compiler to allow this. Most I can hope for is the fact I can maybe use classes now. Then the extension will work a lot differently then the rest of ENIGMA, but still. I don't want a hundred functions just to set a freaking background image.
Logged
Offline (Male) Josh @ Dreamland
Reply #23 Posted on: October 05, 2014, 05:02:20 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
The pastebin link basically tells you exactly how to implement this for your own functions. :P

You don't have to add the cast to variant, but it shows you how to do that, too.
Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Unknown gender) TheExDeus
Reply #24 Posted on: October 06, 2014, 05:02:20 am

Developer
Joined: Apr 2008
Posts: 1860

View Profile
So I can hardcode the compiler codegen as well?
Also, the standard say's _t is reserved, so I guess it's better not to call them window_t.

I'll try using your code then. Though I really have to think hard to understand it. :D
« Last Edit: October 06, 2014, 05:08:47 am by TheExDeus » Logged
Offline (Male) Josh @ Dreamland
Reply #25 Posted on: October 12, 2014, 06:41:28 pm

Prince of all Goldfish
Developer
Location: Pittsburgh, PA, USA
Joined: Feb 2008
Posts: 2950

View Profile Email
The standard says no such thing, but a quick googling shows that POSIX decided to reserve them. I don't really fear the wrath of POSIX reservations, but you can name them how you choose. Try to find a consistent naming scheme—I liked _t, personally, and would still argue for it on the grounds that no standard header has business using type names such as window_t. That said, we can expose them to ENIGMA under whatever naming convention we like, so really I'll only debate you at the enigma_user level.

The method uses empty classes by given names as keys for template specialization—it's an old hack that doesn't show up a whole lot. Template specializations are use to look up type metadata, which is used to tell variant what it holds.
« Last Edit: October 12, 2014, 06:43:05 pm by Josh @ Dreamland » Logged
"That is the single most cryptic piece of code I have ever seen." -Master PobbleWobble
"I disapprove of what you say, but I will defend to the death your right to say it." -Evelyn Beatrice Hall, Friends of Voltaire
Offline (Unknown gender) TheExDeus
Reply #26 Posted on: November 05, 2014, 09:09:23 am

Developer
Joined: Apr 2008
Posts: 1860

View Profile
I can't get this to work in ENIGMA. I added this to var4.h:
Code: [Select]
//For RTTI
namespace enigma{
  struct reference_index {
    int id;
    reference_index(int idx): id(idx) {}
    enum { DNE = -1 };
  };
 
  template<class T> struct derived_reference_index: reference_index {
    derived_reference_index(): reference_index(-1) {}
    derived_reference_index(int idx): reference_index(idx) {}
  };
 
  namespace RTTI {
    template<class T> int compiler_ids();
  }
}

struct variant
{
template<class T> variant(const enigma::derived_reference_index<T>& dri): rval(dri.id), sval( ), type(enigma::vt_real), rtti(enigma::RTTI::compiler_ids<T>()) { }
}
Then I modified var4.cpp to have this:
Code: [Select]
variant::variant(const variant& x): rval(x.rval.d), sval(x.sval), type(x.type), rtti(x.rtti) { }
variant::variant(const var& x): rval((*x).rval.d), sval((*x).sval), type((*x).type), rtti((*x).rtti) { }
variant::variant(): rval(0), sval( ), type(default_type), rtti(-1) { }
In my extension's include.h:
Code: [Select]
namespace enigma {
  namespace RTTI {
    enum variant_runtime_types {
      VRT_WINDOW, 
      VRT_BUTTON
    };
   
    //template<> int compiler_ids<enigma_user::window_t>() { return VRT_WINDOW; }
    template<> int compiler_ids<enigma_user::button_t>() { return VRT_BUTTON; }
  }
}
And button create:
Code: [Select]
variant gui_button_create(){
if (gui::gui_bound_skin == -1){ //Add default one
gui::gui_buttons.insert(pair<unsigned int, gui::gui_button >(gui::gui_buttons_maxid, gui::gui_button()));
}else{
gui::gui_buttons.insert(pair<unsigned int, gui::gui_button >(gui::gui_buttons_maxid, gui::gui_buttons[gui::gui_skins[gui::gui_bound_skin].button_style]));
}
gui::gui_buttons[gui::gui_buttons_maxid].visible = true;
gui::gui_buttons[gui::gui_buttons_maxid].id = gui::gui_buttons_maxid;
return variant(enigma::RTTI::button_t(gui::gui_buttons_maxid++));
}
And I get this:
Code: [Select]
Universal_System/Extensions/BasicGUI/buttons.cpp: In function 'variant enigma_user::gui_button_create(gs_scalar, gs_scalar, gs_scalar, gs_scalar, std::string)':
Universal_System/Extensions/BasicGUI/buttons.cpp:195:65: error: no matching function for call to 'enigma::RTTI::button_t::button_t(unsigned int)'
   return variant(enigma::RTTI::button_t(gui::gui_buttons_maxid++));
                                                                 ^
Universal_System/Extensions/BasicGUI/buttons.cpp:195:65: note: candidates are:
In file included from Universal_System/Extensions/BasicGUI/buttons.cpp:34:0:
Universal_System/Extensions/BasicGUI/include.h:33:12: note: enigma::RTTI::button_t::button_t()
Among a lot of other things. I have tried about 3 hours to fix this without luck. The best I had was "multiple definitions" linker error.

The problem is that your example is self contained, but in ENIGMA I need the var4 changes to work even if my extension is disabled. And as nothing is generated right now, then I need to add this manually in extensions instead of main ENIGMA:
Code: [Select]
namespace enigma {
  namespace RTTI {
    enum variant_runtime_types {
      VRT_WINDOW, 
      VRT_BUTTON
    };
   
    //template<> int compiler_ids<enigma_user::window_t>() { return VRT_WINDOW; }
    template<> int compiler_ids<enigma_user::button_t>() { return VRT_BUTTON; }
  }
}
It seems for now I will make the extension without this, but I really would want this. That would greatly reduce my function count, as I would have "gui_element_sprite" instead of "gui_button_sprite", "gui_window_sprite", "gui_slider_sprite" and so on.
Logged
Pages: « 1 2
  Print