Non-Copyable Structs with Constructor parameters

Community Forums/Monkey2 Talk/Non-Copyable Structs with Constructor parameters

Danilo(Posted 2016) [#1]
I have a small problem here:
An external C++ code defines classes/structs and makes them non-copyable.
I can't import it as class into MX2, because classes in MX2 are pointers.
Need to import it as structs, so I can give those structs to methods like GetData(Chunk& data); // please notice the '&'

For:
Local x := New TheStruct1()
Local y := New TheStruct2(1,2,3)

MX2 generates something like:
TheStruct1 x = TheStruct1();
TheStruct2 y = TheStruct2(1,2,3);

But this generates C++ errors, because it invokes the copy-operator.
Those Structs are made non-copyable.

Instead, in C++ you would initialize it like this:
TheStruct1 x();
TheStruct2 y(1,2,3);

This does not copy the object, it is using the constructor directly.

Shouldn't / Couldn't MX2 also generate direct code for struct construction, without copying?

With Struct in MX2 I can do:
Local x:TheStruct1
Local y:TheStruct2

The problem: I can't find a way to use a Struct-Constructor with parameters, without invoking a copy.

The following (similar to C++) does not work:
Local y:TheStruct2(1,2,3)


Any thoughts?


marksibly(Posted 2016) [#2]
Alas, there's no 'nice' way to do this currently, ie: you'll probably need some native glue code here.

Even if mx2 could generate code for 'initialization' constructors, that wouldn't be the end of it - some new 'NonCopyable' mechanism would need to be added to prevent people trying to copy them (or they'd get weird c++ compile errors), and there would likely be a cascade of other stuff to deal with. And non-copyable structs don't really make sense to mx2 - the whole points of structs is that they can *only* be copied!

Again, the problem I have with trying to 'force' mx2 to work like c++ is that there are 1001 esoteric/crazy things you can do in c++, and trying to accomodate ALL of them just wont work. This is also a relatively 'rare' pattern in my experience.

If 'GetData' took a pointer instead of a reference, you could use an' extends void' class. So I guess you could add the 'glue' to GetData, eg:

//c++
void GetData_glue( MyStruct *p ){
   GetData( *p );   //calls 'real' GetData
}

'monkey2...
Extern

Function GetData( t:MyStruct )="GetData_glue"

Class MyStruct Extends Void
   Method New( x:int,y:int,z:int )
End

Function Main()
   Local t:=New MyStruct( 1,2,3 )
   GetData( t )
End   


Depending on what the API actually looks like, my advice would probably be to wrap 'GetData' and hide MyStruct from the user entirely if possible.


Danilo(Posted 2016) [#3]
C++ example for typical SFML that shows the direct constructor usage: http://www.edparrish.net/common/sfml-osx.html
#include <SFML/Graphics.hpp>

int main()
{
    sf::RenderWindow win(sf::VideoMode(200, 200), "SFML Test");
    sf::CircleShape shape(100.f);
    shape.setFillColor(sf::Color::Green);

    while (win.isOpen())
    {
        sf::Event event;
        while (win.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                win.close();
            }
        }

        win.clear();
        win.draw(shape);
        win.display();
    }

    return 0;
}

In C++ often pointers and references are used mixed:
void setTexture(const Texture* texture, bool resetRect = false);
void setTextureRect(const IntRect& rect);
void setFillColor(const Color& color);
const Color& getFillColor() const;

void clear(const Color& color = Color(0, 0, 0, 255));
void draw(const Drawable& drawable, const RenderStates& states = RenderStates::Default);
void draw(const Vertex* vertices, std::size_t vertexCount, PrimitiveType type, const RenderStates& states = RenderStates::Default);

The hardest thing to figure out is what to import as structs and what as classes. :)

Pointers like Texture* are easy (as class), but with structs it's not that easy, especially
when copy-operator is disallowed by the external lib.


marksibly(Posted 2016) [#4]
Looking through the API, I think the 'NonCopyable' stuff is really meant to indicate 'should be created with new' (similar to making something a class in mx2). For example, this...

sf::RenderWindow window(sf::VideoMode(800, 600), "SFML window");

...is pretty much useless in 'real code' as the window will be destroyed when it goes out of scope (not to mention that you can't actually 'copy' or store the created window anywhere, so how does other code reference it?). In real code, you'd probably use...

sf::RenderWindow *window=new sf::RenderWindow(sf::VideoMode(800, 600), "SFML window");

...so you end up with a pointer to the window that can be stored in a var, copied etc. and doesn't get destroyed when it goes out of scope. But of course the first version looks cooler in a 'hello world' demo.

Note that this is a 'very c++' library, and you might be better off trying to wrap 'CSFML' which is the 'C' wrapper. This appears to be what all the other wrappers wrap too.

If you want to try anyway though, my advice would be to implement all the NonCopyable stuff using 'extends void' classes:

http://www.sfml-dev.org/documentation/2.3.2/classsf_1_1NonCopyable.php

These are all types that, IMO anyway, look like they are supposed to 'persist' and should generally be created with new. Kind of counter-intuitive given the demo code, but I think I get what the author is doing - by making them NonCopyable, you can still use them 'on the stack', but it forces you to use 'new' if you want to share them.

Anything that is passed to functions/methods via 'const &' or by value can go in a struct, eg: Color, IntRect.

There will be conflicts though, eg: the Drawable 'draw' method...

virtual void sf::Drawable::draw( RenderTarget &target,RenderStates states ) const;

RenderTarget is non-copyable, so you want this to be an 'extends void' class. But objects are stored in a pointer and this method wants a c++ 'reference' (which is really just a kind of pointer...) so you'll need some glue code to convert a RenderTarget pointer to a RenderTarget reference, eg:

//note: assumes Drawable is a 'struct' so is passed by 'const &'.
void Drawable_draw_glue( const Drawable &d,RenderTarget *target,RenderStates states ){
d.draw( *target,states );
}

Again, it might be worth looking at CSFML instead!


Nobuyuki(Posted 2016) [#5]
>Again, the problem I have with trying to 'force' mx2 to work like c++ is that there are 1001 esoteric/crazy things you can do in c++, and trying to accomodate ALL of them just wont work.

Whatever you do, please don't make it too complicated with c++-like tricky patterns. One of the main goals I know was to make externs easier, but mx2's still gotta have some "pick up and go" and that may mean abandoning some edge cases for the sake of language simplicity.


Shinkiro1(Posted 2016) [#6]
> ... and that may mean abandoning some edge cases for the sake of language simplicity.
This.