question

Sue M6 avatar image
0 Likes"
Sue M6 asked Sue M6 commented

How to bind operator

Hello,

I am trying to bind operator for my custom class, Here is the code:

class TestClass : public SimpleDataType
{
public:
TestClass() {}
~TestClass() override {}

void bind() override
{
SimpleDataType::bind();
bindStlContainer(vec);
}

void bindInterface() override
{
bindBracketOperator(int, TestClass, int);
bindOperator([], TestClass, "int (int index)");
}

int operator[](int index) { return vec[index]; }

private:
std::vector<int> vec = { 1, 2, 3 };
};

When I use the operator[] in flexsim(index = 0), both bindBracketOPeraor and bindOPerator throw exception for "vector subscript out of range", as the value of vec shown in flexsim is exactly {1, 2 ,3}, so how can I bind the operator[] by the two different ways(bindBracketOPeraor and bindOPerator)?

Thanks!

FlexSim 20.0.10
bind
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

1 Answer

Jordan Johnson avatar image
0 Likes"
Jordan Johnson answered Sue M6 commented

In a module, the bindBracketOperator() function can only bind bracket operators that don't return an int or a double.

Even if you could, though, I think you'd really want to return and int& or a double&, so you could modify the value. But it turns out you can't return those values either.

The solution: create your own reference class and bind that. Here's some example code:

class IntRef
{
private:
    int* ptr = nullptr;

public:
    IntRef() {}
    IntRef(int* ptr) : ptr(ptr) {}

    operator int() {
        return *ptr;
    }

    operator Variant() {
        return *ptr;
    }

    int operator +(int other) { 
        return *ptr + other;
    }

    IntRef operator =(int other) {
        *ptr = other;
        return IntRef(ptr);
    }

    void bindInterface() {
        auto addOp = &IntRef::operator+;
        SimpleDataType::bindOperatorByName<decltype(addOp)>("+", addOp, "int plus(int)");

        auto assignOp = (IntRef(IntRef::*)(int))&IntRef::operator=;
        SimpleDataType::bindOperatorByName<decltype(assignOp)>("=", assignOp, "IntRef assign(int)");

        bindCastOperator(int, &IntRef::operator int);
        bindCastOperator(Variant, &IntRef::operator FlexSim::Variant);
    }
};

class TestClass : public SimpleDataType
{
public:
    TestClass() {}
    ~TestClass() override {}

    static TestClass* init(TreeNode* target) {
        TestClass* test = new TestClass();
        target->addSimpleData(test, 1);
        return test;
    }

    const char* getClassFactory() override { return "Demo::TestClass"; }

    void bind() override
    {
        SimpleDataType::bind();
        bindStlContainer(vec);
    }

    void bindInterface() override
    {
        bindClass(IntRef);
        
        bindMethod(init, TestClass, "TestClass init(treenode target)", BIND_METHOD_STATIC);
        bindMethod(get, TestClass, "int get(int index)");
        bindMethod(set, TestClass, "void set(int index, int value)");

        SimpleDataType::bindBracketOperatorByName<IntRef, TestClass>("IntRef", &TestClass::operator[]);
    }

    int get(int index) { return vec[index]; }
    void set(int index, int value) { vec[index] = value; }

    IntRef operator[](int index) { 
        return IntRef(vec.data() + index); 
    }

private:
    std::vector<int> vec = { 1, 2, 3 };
};

With this class, you can run the following code:

TestClass t = TestClass.init(Model.find("Tools").subnodes.assert("test"));
t[0] = 1234;
return [t[0], t[1]];
// returns Array[2]: 1234, 2

Note: this essentially allows you to deal in raw memory addresses. If you modify the vector, it may reallocate its contents. Attempting to use an IntRef that no longer points to valid memory will, at best, crash your program, and at worst, cause some other strange bug that you spend forever tracking down.

Also note: you may need to implement additional cast or assignment operators on the IntRef class.

· 4
5 |100000

Up to 12 attachments (including images) can be used with a maximum of 23.8 MiB each and 47.7 MiB total.

Sue M6 avatar image Sue M6 commented ·
Thank you very much for your help!
0 Likes 0 ·
Sue M6 avatar image Sue M6 commented ·

Hi, @Jordan Johnson , I create a std::map member variable, it works like the label in flexsim, here is the code:

class TestClass : public SimpleDataType
{
public:
    TestClass() {}
    ~TestClass() override {}

    static TestClass* init(TreeNode* target) {
        TestClass* test = new TestClass();
        target->addSimpleData(test, 1);
        return test;
    }
 
    void bind() override
    {
        SimpleDataType::bind();
        bindStlMap(_userData);
    }
 
    void bindInterface() override
    {
        bindMethod(init, TestClass, "TestClass init(treenode target)", BIND_METHOD_STATIC);
        SimpleDataType::bindBracketOperatorByName("Variant", &TestClass::operator []);
    }
 
    Variant& operator[](const char* key)
    {
        return _userData[key];
    }
 
private:
    std::map<std::string, Variant> _userData;
};

The code in flexsim:

treenode test = Model.find("test");
TestClass tc = TestClass.init(test);
tc["key1"] = 1;
tc["key2"] = 2;
tc["key3"] = 3;
return tc["key1"];

_userData insert only one key-value, and the key is unreadable code, how can I make the _userdata to work?

Thanks!

0 Likes 0 ·
Jordan Johnson avatar image Jordan Johnson ♦♦ Sue M6 commented ·

The problem is that FlexScript currently doesn't use reference types as return values. Instead, you have to create a non-reference type that wraps a pointer or iterator, and return that object. Here's an implementation of an ordered map in a module. Note that for unordered maps, I would just use version 21.0+, which has a Map type natively in FlexScript; it supports Variant keys and Variant values.


WARNING: as with the previous example, this code lets you create values that are no longer safe to use if you modify the map.

typedef std::map<std::string, Variant> StrMap;

class StrMapIter
{
private:
  StrMap::iterator endIter;
  StrMap::iterator iter;

public:
  StrMapIter() {}
  StrMapIter(const StrMap::iterator& iter, const StrMap::iterator& endIter) : iter(iter), endIter(endIter) {}
  StrMapIter(const StrMapIter& other) : iter(other.iter), endIter(other.endIter) {}

  void construct() { new (this) StrMapIter(); }
  void construct(const StrMapIter& other) { new (this) StrMapIter(other); }
  void destruct() { this->~StrMapIter(); }

  const char* _getKey() { return iter->first.c_str(); }
  __declspec(property(get = _getKey)) const char* key;

  Variant _getValue() { return iter->second; }
  void _setValue(const Variant& value) { iter->second = value; }
  __declspec(property(get = _getValue, put = _setValue)) Variant value;

  operator Variant() { return iter->second; }
  operator int() { return iter != endIter ? 1 : 0; }

  StrMapIter operator =(const Variant& other) {
    iter->second = other;
    return StrMapIter(*this);
  }

  StrMapIter& operator=(const StrMapIter& other) {
    iter = other.iter;
    endIter = other.endIter;
    return *this;
  }

  StrMapIter operator++(int) {
    StrMapIter temp(*this);
    ++iter;
    return temp;
  }

  void bindInterface() {
    SimpleDataType::bindConstructor(&StrMapIter::construct, "void StrMapIter()");
    SimpleDataType::bindCopyConstructor<StrMapIter>(&StrMapIter::construct);
    SimpleDataType::bindCopyAssigner<StrMapIter>(&StrMapIter::operator =);
    SimpleDataType::bindDestructor<StrMapIter>(&StrMapIter::destruct);

    auto assignOp = (StrMapIter(StrMapIter::*)(const Variant&))&StrMapIter::operator=;
    SimpleDataType::bindOperatorByName<decltype(assignOp)>("=", assignOp, "StrMapIter assign(Variant&)");

    bindCastOperator(Variant, &StrMapIter::operator Variant);
    bindCastOperator(int, &StrMapIter::operator int);

    // A bug switches ++/-- in version 20.0
    auto incOp = &StrMapIter::operator++;
    SimpleDataType::bindOperatorByName<decltype(incOp)>("--", incOp, "StrMapIter inc()");

    SimpleDataType::bindTypedPropertyByName<std::string>("key", "char*", force_cast<void*>(&StrMapIter::_getKey), nullptr);
    bindProperty(value, &StrMapIter::_getValue, &StrMapIter::_setValue);
  }
};

class TestMap : public SimpleDataType
{
private:
  StrMap map;

public:
  static TestMap* init(TreeNode* target) {
    TestMap* test = new TestMap();
    target->addSimpleData(test, 1);
    return test;
  }

  StrMapIter operator[](const char* key) {
    auto result = map.insert({ key, Variant() });
    return StrMapIter(result.first, map.end());
  }

  StrMapIter begin() {
    return StrMapIter(map.begin(), map.end());
  }

  StrMapIter find(const char* key) {
    return StrMapIter(map.find(key), map.end());
  }

  Array _getKeys() {
    Array k;
    for (const auto& pair : map) {
      k.push(pair.first);
    }
    return k;
  }
  __declspec(property(get = keys)) Array keys;
  
  const char* getClassFactory() override { return "Demo::TestMap"; }

  void bind() override
  {
    SimpleDataType::bind();
    
    // The rest of this code essentially does the same thing
    // as bindStlMap(), but using that directly introduced linker errors
    // When you save, you have to convert your structure to a tree,
    // and reverse the process when you load.
    TreeNode* mapNode;
    bindSubNodeByName("map", mapNode, 0);
    auto subnodes = mapNode->subnodes;

    int bindMode = getBindMode();
    
    if (bindMode == SDT_BIND_ON_LOAD) {
      for (int i = 1; i <= subnodes.length; i++) {
        map.insert({ subnodes[i]->name, subnodes[i]->value });
      }
    }

    if (bindMode == SDT_BIND_ON_SAVE) {
      for (const auto& pair : map) {
        subnodes._assert(pair.first.c_str())->value = pair.second;
      }
    } else {
      mapNode->destroy();
    }
  }

  void bindInterface() override {
    bindClass(StrMapIter);

    bindMethod(init, TestMap, "TestMap init(treenode target)", BIND_METHOD_STATIC);
    bindBracketOperator(StrMapIter, TestMap, const char*);
    bindMethod(find, TestMap, "StrMapIter find(char*)");
    bindMethod(begin, TestMap, "StrMapIter begin()");
    SimpleDataType::bindTypedPropertyByName<Array>("keys", "Array", force_cast<void*>(&TestMap::_getKeys), nullptr);
  }
};

If you set up your class this way, you can write code like the following:

treenode target = Model.find("Tools").subnodes.assert("map");
TestMap map = TestMap.init(target);
map["hi"] = "there";
string hello = "hello";
map[hello] = "world";

map["c"] = 10;
map["a"] = 20;
map["z"] = 30;

for (var iter = map.begin(); iter; iter++) {
  print(iter.key + ": ", iter.value);
}

This prints the values in the map in alphabetical order.

0 Likes 0 ·
Sue M6 avatar image Sue M6 Jordan Johnson ♦♦ commented ·

Thanks, I prefer to bind get/set method to modify the map data :)

0 Likes 0 ·