Advertisement

Hardcoding geometry / initializer lists as expressions?

Started by July 03, 2012 09:44 PM
7 comments, last by WitchLord 12 years, 5 months ago
I've been trying to find a good way to facilitate terse, hardcoded vertex data in my engine's scripting library -- the engine in question defines all its graphics based on polygon outlines, and is meant to facilitate generative art and (now that I'm adding script support) quick prototyping. If at all possible I'd like to find a decent way to avoid needing an identifier (like [font=courier new,courier,monospace]Point(...)[/font] or [font=courier new,courier,monospace].add(...)[/font]) with every pair of coordinates.

My C++ method is reliant on a temporary class and a function-call ([font=courier new,courier,monospace]operator()[/font]) overload, which Angelscript doesn't support by design and is not a likely future addition based on remarks by Andreas:


myShape.build(Shape::LINE_LOOP)(-.5f, -.4f)(.0f, .4f)(.5f, -.4f);


Another possible (though inelegant) means of doing it would be a variable-parameter-count function call (the generic calling convention?), but I haven't seen evidence of support for such in function registration.

Yesterday it struck me that an extremely attractive solution (clearer still than my C++ one, if less terse) would be using array initializer lists:

Point p = {.5f, .2f};
myShape.build(Shape::POLYGON, {{-.5f, -.4f}, {.0f, .4f}, {.5f, -.4f}});
// or perhaps
myShape.add(Shape::POLYGON) = {{-.5f, -.4f}, {.0f, .4f}, {.5f, -.4f}};

...I didn't expect to see this sort of behavior outlawed, but unfortunately it is, for reasons which I assume relate to static typing.
I could impose the extra step of creating an array of type float[][] or Point[], but the former discards its elegance while keeping the caveat of inner arrays allowing incorrect sizes, and the latter requires a slew of constructors which is what I'm trying to avoid in the first place.


I'm curious to hear any solutions or ideas folks here might have.


I'm getting tempted to write a preprocessor that turns the token ".(" into ".opFunc(" or something to that effect, but it's likely to be a messy solution and create inconsistencies with normal script behavior. I could go a step further and attempt to implement a proper function-call operator in angelscript's compiler, but this may be overkill given the work involved and that I'd need to merge the modification with each new version of the library as I updated.
Still, it's tempting, and I've taken the initiative of digging around in the parser/compiler/tokenizer code.
Hah, well, my investigation of the compiler code has enlightened me in two ways:

1. It's hard to do a casual code dive in a compiler.

2. Function-call syntax has to sort out a ton of ambiguities, most relevantly the one between an operator() on a member property and a call to a member function.


I'm still really fascinated with the notion of performing some kind of substitution where the error [font=courier new,courier,monospace]TXT_NOT_A_FUNC_s_IS_VAR [/font]occurs. Any insight you might be able to give on the possibility of this, Andreas?

EDIT: But it doesn't hit that error in the chained-parenthesis case. Darn.
Advertisement
Anomymous lists/arrays/tables like this is something I enjoy a lot in other languages, particularly in dynamically typed languages, but even in the more limited form that is used in C++. I would very much like to add support for this in AngelScript too, but I've not had time to think about all the problems that it will create for the compiler.

The biggest problem with initializer lists in expressions is definitely due to the static typing. What type would the initializer list have?

In variable declarations, the type is known already before evaluating the initialization list, so there it is easy to determine what code to generate, but within a random expression it is much more difficult.

Without any changes to the script engine, the best is probably to support the generation of your polygons with a float array, like this:



float[] a = {-.5f, -.4f, .0f, .4f, .5f, -.4f};
myShape.build(Shape::POLYGON, a);



I welcome any suggestions for how this can be made to work well in a statically typed language like AngelScript.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game


1. It's hard to do a casual code dive in a compiler.


I spent little over 9 years developing the compiler already, and even I get lost some times. :)


2. Function-call syntax has to sort out a ton of ambiguities, most relevantly the one between an operator() on a member property and a call to a member function.


I'm still really fascinated with the notion of performing some kind of substitution where the error [font=courier new,courier,monospace]TXT_NOT_A_FUNC_s_IS_VAR [/font]occurs. Any insight you might be able to give on the possibility of this, Andreas?

EDIT: But it doesn't hit that error in the chained-parenthesis case. Darn.
[/quote]

TheAtom reported a bug today about making calls with function defs. I still need to investigate what changes needs to be made to support those scenarios, but as they look similar in syntax to what the use of a function call operator might look like I recommend you wait a little until I've fixed that bug before you dig too deep into the code.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Maybe an expression syntax like TYPE{element, element, ...} ?

That would allow me to do something like this:

typedef float[][] Vertices;

// ...

myShape.build(Piece_Polygon, Vertices{{1.5f, 2.0f}, {-5.0f, 3.1f}, {6.1f, 3.2f}});


Admittedly type inference would be way nicer but I understand it's a non-trivial problem when things like function overloads are involved. :/


EDIT: Definitely okay with waiting a while if it means a possibility of function-call operators. :)
Would it be possible to allow variable declarations as parameters? Something like this:


useArray(float[] array = {-.5f, -.4f, .0f});
useNestedArrays(float[][] array = { {1.5f, 2.0f}, {-5.0f, 3.1f} });


I also like Cellulose's example above, but it would be nice to not have to use a typedef:


useArray(float[] {-.5f, -.4f, .0f});
useNestedArrays(float[][] { {1.5f, 2.0f}, {-5.0f, 3.1f} });
Advertisement
I was thinking about the possibility to defer the evaluation of the initialization list until it is known what it will be used for. Whenever the compiler encounters an initialization list in an expression it would then first look for possible types that can be initialized with initialization lists, e.g. while matching function overloads an initialization list in argument would then match only to the function overloads whose parameter is an array.

In most cases it would then be possible to avoid the need to explicitly inform the exact type of the initialization list. However whenever there is an ambiguity to resolve it would be necessary to explicitly inform the type. In this case I would prefer to use something similar to value casts, possibly without the extra () as the list is already enclosed in {}.

This would match Jake's last example, i.e.



// To resolve ambiguity it's necessary to inform the type of the list
useArray(float[]( {-.5f, -.4f, .0f} ));
useNestedArrays(float[][] { {1.5f, 2.0f}, {-5.0f, 3.1f} } ); // Parenthesis are optional
// When there is no ambiguity, it's not necessary to inform the type
onlyFloatArray({0,1,2,3});



To make it more useful it would also be necessary to allow use of initialization lists to create arbitrary objects, like vector, etc. Here I think it would be possible to match each element in the list to one of the arguments of a constructor. This would allow something like this:


// Assuming the vector2 has a constructor like vector2(float, float)
vector2 vec = {0,0};
vector2[] arr = {{1,2}, {3,4}};



I believe this would work. Currently I don't see anything that wouldn't work with this syntax. I'll have to make a more detailed analysis to make sure though.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

For my part, I think the initialization-list constructor should be something that's defined manually somehow, to prevent an ambiguity between constructors which take arrays as their arguments and those which take initialization lists -- for instance, an array<int> has a 2-int constructor where the first specifies a size and the second specifies an element to be copied to all indices, but when we pass it an initialization list we want a size of two and the given elements.

There might be a better way around this but I wouldn't know what it was.

Also thanks -- this is going to be an immensely awesome language feature.
Yes, you're right.

I believe it may be enough for the compiler to check for the existance of the asBEHAVE_LIST_FACTORY. If it exists for the type, then only that one will match the initialization list. If it doesn't exist, then any of the factories/constructors can be used.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement