Best way to store Value and Ref* in one Map?

With CCArray, and CCObject derived objects, I could have my game objects, coordinates, strings, and floats, etc, all next to one another in one CCDictionary. It was very convenient.

I’ve made the jump to 3.3 from 2.x.x, and noticed that ValueMaps, ValueVectors, etc, do not inherently have this same functionality as CCArray does.

I’m struggling to figure out how to do this in 3.3 with Templated maps or ValueMaps.

For now, I’ve made a ValueRef class that inherits Ref*, and just has a Value inside, and I’m just sticking this in a std::vector<Ref*>, so I can have it alongside GameObjects etc.

I’d love to just use ValueMap and ValueVector, but I cannot see a graceful way of putting Ref* objects in a ValueMap. I do not understand Value’s implementation and memory management enough to know if we can just put Ref*'s as Values, as that would be the best.

Any ideas?

Thanks!

You can also use setObject/getObject on the Node to store the ValueRef in the Node itself.

Thanks Mannewalis,

Ugh, so in retrospect it’s much more convenient to be able to use ValueMap/ValueVector that can store Ref* than a vector<Ref*> that can store Values. As 90% of the information are Value objects, and Ref* are the rarity.

So I’m going to try and modify Value to take Ref* as it’s value. Just retain() and release() it along with the Value’s lifecycle, I think it should work… Tips & advice welcome!

Can you just store the game node ID? Give each a name or a tag, and store that? What is the reason you need GameObjects(Nodes) alongside the value datetypes?

I did also have to migrate from using Ref* dictionaries to ValueMap + reference Map<string,GameObject*> where I look up the game objects in the array by an id (currently string, but may hash out to an int32 in the future). The one thing I think that would improve Value is the ability to host a Vec2 or Vec3, but then when I need that it’s always homogenous and thus I can just use the STL std::vector<Vec2>. Ideally I’m trying to move over to pure STL where I can. std::vector<string>, std::vector<struct GameModel>.

While I understand wanting to host both in the same container, this might allow you to think of other ways to handle your problem, for better or worse.

Thanks Steve,

Okay, so store an ID, then go look it up in another list? Do you keep a specific list of Ref* objects that you would want to look up?
For me, that would require traversing the scene graph or something to find the actual node. It was just so easy before to store pointers to objects.

My situation:
So I have abilities that are defined and maintained by Dictionaries. “duration” as a float, “targetPoint” as a GameObject wrapped CCPoint just as you mentioned, wanting to store Vec2’s alongside everything else. I also stored pointers to the abilities Sprite, and a pointer to the casting hero Game Object.
e.g.
“name” = (string)“fireball”
“damage” = (float)“100”
“targetPoint” = (GamePoint*) - CCObject CCPoint wrapper class.
“castingHero” = (GameHero*) - CCObject hero class.
“sprite” = (CCSprite*) - the fireball’s sprite and position on screen.

I was throwing Dictionaries around all over to define attack events, collision events pointing at two box2D bodies and the impulse etc. It was great.
It all worked so well!
I miss Arrays and Dictionaries =(

I’m all for moving to better systems, but this hurts.

As for your issue, my first question is how long are you storing these dictionary wrappers? Can you create a struct and pass that around (as const reference where possible)? Are you mostly using these in callbacks or UI action methods? I found that I was able to use the new c++11 lambdas and closures to mitigate some of the need for passing Dictionaries around. Do you need the need for retain/release on the elements of Ability which is the main purpose of Array/Dictionary? If not you could just use a vector/map of Abilities.

struct Ability {
    string name;
    float damage;
    Vec2 targetPoint;
    GameHero* castingHero;
    Sprite* sprite;
};
std::vector<Ability> abilities; 
// note you should pass by ref to avoid excess copying
void A::method(const std::vector<Ability>& abilityReference) {}
method(abilities);

Some other random thoughts/notes

If you’re trying to release soon I think you can continue using __Array and __Dictionary? I believe I still was using them even after the jump to 3.3, but that update did prompt me to finally get rid of all use of all deprecated classes/methods/etc.

I think the main reason for the change is they’re moving toward finally getting rid of the objective-c style memory management of retain/(auto)release. Every time you create a wrapper dictionary you are retaining and releasing. You could look into moving toward full STL with vector<shared_prt>, but as you say it was working well for you before.

Another solution to your problem would be serialization. So you could serialize the dictionary and effectively pass a string around and deserialize in the receiving method. This would be useful if you also wanted to save/load this information which I’m guessing you do not.

With IDs you do have to make sure they’re unique through the entire time they’ll be referenced. This could be a GUID for entire app run, or maybe you only need them unique per level if everything is cleared out, etc. It is a different way of solving your problem, and I’m using it now as I’ve been refactoring some aspects of the game into a more entity-component design.

I do have collections of game objects: AllUnits, PlayerUnits, EnemyUnits, AllyUnits, Projectiles, etc. alongside managers: UnitManager, ProjectileManager, etc. I have simple inline getters that lookup units/projectiles based on their guid.

note: I do remove from these collections on the next frame, so I check isActive() on objects and remove them at the start of every call to update so that they’re not used after they’re removed (removed by collision, referenced by another collision, for example)

Well, some dictionaries lasted the entire game. e.g. I have an array of dictionaries that define what skills a hero has.
Then when they cast the skill, a copy of it is created and sent to the skill manager, which manages the lifecycle of the skill, creation of sprites, counting down timers, applying delayed effects etc. Those copies will last between one update cycle to 10 seconds or so.

I started making ability structs or even classes to handle ability specific conditions, but it got annoying as many abilities were just slightly different. e.g. a fireball vs an arrow. Both are projectiles, but one creates an explosion, and thus has an explosion spec and always plays one sound, the other does direct damage, and plays a specific sound depending on what it hits etc. So it got really annoying to make a new struct AND put the code somewhere to handle the logic for the skill.

So… I just put all the code in one skill manager class, and let everyone send dictionaries with whatever information was needed for that skill. Again, this was lots of floats, sound and art filenames strings, as well as game objects and sprites.
I even handled the sprite life cycles, where the only thing that knew about the sprite was the actual drawing node, and the dictionary of the skill that spawned it. I would then tell the sprite to remove itself from parent, then remove the dictionary from the “active skills”, and everything gets released.

So yeah the retain/release cycle of my Array and Dictionary elements was very important, as I used the dictionary as the only representation of an active skill and all the effects, sounds, game objects, characters, and sprites related to the effect/skill.

I thought about just sticking to __Array and __Dictionary… I dunno, again, I’m all for new & better systems, so I’m willing to put in work if it’s going to be better… Not sure about the better, but I’m sure I can get it to work.

So now I’m trying to wrap my head around cocos:Vector::pushBack vs std::vector::push_back

I understand cocos::Vector::pushBack maintains the obj-c style retain/release paradigm.

So if I use std::vector::push_back( cocos2d::Value ), how is the Value’s lifecycle managed? It has no idea it’s being held onto right? Or does it just adopt the scope of the std::vector it’s in via std::moves (or copies)?

If so, I think I’m pretty happy with the solution of just letting cocos2d::Value objects point at Ref* objects. I just retain/release the Ref* object when I create/delete the cocos2d::Value.

Although I still can’t test any of this because I’m still hundreds of errors from being able to compile my first 3.3 build hahaha damnit =/
I’ve seriously spent the past 3 days on this 2.5 to 3.3 move. Well this and lying fetal position in the shower until the hot water runs out, but mostly this.

Thanks again Steve

Haha, I think our code migration from 2.x to 3.x was roughly a week to compile and three weeks to switch over to c++11 style containers, callbacks, and whatever necessary to eliminate using deprecated methods figuring that it would help in the long-term. Go take a break, migrating or refactoring are mind-numbing tasks :frowning:

Yep, cocos2d::Vector will retain/release upon pushBack and eraseObject whereas std::vector will not retain/release, so it assumes a weak pointer, unless you use c++11 memory management with shared_ptr and the like.

We do use cocos2d::Vector and cocos2d::Map for the collections of characters, projectiles, etc. So your design may be just as good or better :smile:.

From what you describe I think adding the ability for Value to hold a retain/release Ref* is the best way forward. At least until everything compiles and you upgrade to 3.4, heh. Then you can maybe see if it makes sense to find a way to remove the need to hold onto the Ref* objects later?

But yeah, go get it to compile. If you haven’t refactored too much, I’d say stick with __Array and __Dictionary until your ready. A build succeeded is a big emotional win, heh.

What do you not like about using cocos2d::Vector and cocos2d::Map for characters, projectiles, and other Ref* object collections? This continues to use the obj-c style retain/release cycle right? Just want to get away from this?

Ugh now I’m in an awkward position where I use cocos2d::Vector<Ref*> whenever I can, but have to resort to ValueVectors with Values that retain/release Ref* objects also. I’m not happy with the split implementation, but I guess it works.

I’m too far along now to revert back to __Array & __Dictionary.

I’m still kinda surprised that storing Values alongside Ref* objects was so uncommon that it was not built-in somehow.
Shows how interesting it can be to see how people go about coding their solutions, and often how solutions come about as a result of the tools at your disposal.

Thanks again for the help,
I’ll post an update and post-mortem on my solutions when I get it compiling and running, whenever that is.

We are actually using cocos2d::Vector|Map for characters, etc. I came from cocos2d-iPhone so retain/release is fine by me and I enjoy that I mostly don’t have to think about memory management while writing c++ code, which is unusual.

One reason for creating ValueVector and ValueMap was for parsing PLIST files, and which we now use when parsing JSON (we used to convert all our source config JSON into PLIST, now we just use directly).

I liked the old __Array & __Dictionary, but I wanted to use the lot of other features in 3.x (namely c++11 improvements) and decided to just move away from all deprecated methods for a cleaner path forward with the engine. I’ve since gone forward using ValueVector for cocos2d::Value types, c++11 std::vector for non-cocos2d::Value value types like Vec2 and custom structs, and all Ref* derived classes with cocos2d::Vector like GameObject*, Character*, Sprite*, Projectile*.

We do have a common GameObject* that is the base for all Character, Projectile, Weapon so we can store any combination of those classes. We don’t store Vec2 in the same collection as string or GameObject*, however.

You could store a vector or map of Abilities like this:

class Ability : public cocos2d::Ref* {
 cocos2d::ValueMap spellInfo;
 cocos2d::Map<std::string,cocos2d::Ref*> spellRefs;
}  

As you say it is “split”, but maybe it’d be the quickest way for you to finish?

I think the main rub for you at the moment is you don’t want any additional work beyond just porting code, so changing how you reference and access data and pointers is not wanted at this point.

We were essentially in the same boat 3 months ago when we moved from 2.x to 3.x so I am not trying to argue in favor of 3.x “split” functionality as you call it, just trying to advise on our path in case it helps yours.

Anyway, probably written enough discussion on it for now, but maybe I’ll go back and see how we changed from __Dictionary to ValueMap. I know that now I do feel like our code and architecture is better, but that also includes regular refactoring having learned within previous year.

If you have any other questions, definitely post them if you haven’t. It’ll help everyone who wants to migrate.

Last tip until then is I highly recommend commenting out large chunks of code with an easy way to know you did this whether through #warning pragmas or some unique word // C2D3MIGRATION: need to uncomment and migrate this method. Getting it to compile is a huge huge win!

It’d be interesting to hear about the post-mortem of sorts. Godspeed.

Went back quick to look through the pre-migration project. It looks like we didn’t have a ton of instances where we were storing objects, but I’ll show an example case. Also, note that it’s often nice to be able to create configuration files (JSON, LUA, txt) with unique ids that are known prior to game start, and thus we have a way to force set the next guid that we calculate as the highest guid used in the predefined config data files and mission scripts.

Again this may be a poor solution, but it’s working for us. Maybe it’ll help? Maybe not?

I will say I very much disliked having to use CCString for both strings and passing around Vec2 and also CCInteger/etc. So a major gain getting rid of the need for ccs() macro and our custom macros for writing “set int into dictionary” macros DICT_GETINT(dict, key, defaultIfMissing).

//------------------------------------------
// Old way

auto dict = CCDictionary::create();
dict->setObject(CCInteger::create(damage), "damage");
dict->setObject(ccs(this->fullName()), "name");
dict->setObject(crewSelected, "initiator");
dict->setObject(target, "target");
attackWithDict(dict);

//------------------------------------------
// New way

// count up implementation of guid
// better to use uint32_t or size_t (but no cc::Value support)
// but this can handle a billion unique instances
static int nextId = 0;
int GameManager::nextGUID() {
    return nextId++;
}
void GameManager::setMinimumGUID(int seed) {
    if(seed > 0 && nextId < seed)
        nextId = seed;
}

// all game objects (and few other classes) get a guid
GameObject::create(...) { 
    //...
    auto gom = GameObjectManager::getInstance();;
    this->setGuid(gom->nextGUID());
    gom->addObject(this);
    //... 
}

//GameObjectManager.h
Map<int,GameObject*> _objectByGuid;
void GameObjectManager::addObject(GameObject* go) {
    _objectByGuid.insert(go->getGuid(), go);
}
GameObject* GameObjectManager::objectByGuid(size_t guid) {
    if(_objectByGuid.find(guid) != _objectByGuid.end()) 
        return _objectByGuid.at(guid);
    return nullptr;
}

// Send off dict for later reference
ValueMap dict;
dict["attack_damage"] = Value(damage);
dict["object_name"] = Value(crewSelected->fullName());
dict["object_guid"] = Value(crewSelected->getGuid());
dict["target_guid"] = Value(target->getGuid());
attackWithDict(dict);

// pull out later
auto guid = dict["object_guid"];
auto gom = GameObjectManager::getInstance();
auto entity = gom->objectByGuid(guid);
1 Like

Yeah I came from full Obj-C so I was totally comfortable with retain/release.

Yeah I had the same thought about ValueVector and ValueMap, as I’m constantly creating dictionaries/maps/arrays from PLISTS, and it was just so convenient to use the built-in parsers.

Yeah the retain/release of cocos::Vector and cocos:Map is very useful and comfortable for us guys coming from iOS when dealing with Ref* objects, which everything is in my game. So just like you I’m using the cocos collections for all my game objects. But then using ValueMap and ValueVector for all my PLIST driven stuff.

On the surface it looks like it will work great, except you have to know when to use .push_back vs .pushBack. I just don’t like that. Maybe if it was just called .pushObject or something to be explicit that it’s going to be retain/releasing the object while it’s in the list.

This is definitely the quickest way to finish, so I’m going forward with it.

hahaha yeah I’ve been commenting out all my scheduleOnce calls because I haven’t figured out the best way to replace them. My comments are not very structured, as they started as // TODO::V3, but have devolved into //omfg
But my Trello board has a task for “//omfg”, so at least I’m organized about it =)

Yeah I was not a fan of passing CCStrings around everywhere. I saw CCString::create() pop up a few times while profiling. I was able to work around most of the obvious places where it was being called over and over, but still. I guess I can’t complain either because it was rather easy to use, and it worked.

Anyway,
Ohh yeah so on the topic of migration issues, I used object->scheduleOnce( schedule_selector( CCSprite::removeFromParent, 1.0f ) a lot.
It looks like the new CC_SCHEDULE_SELECTOR has to take a float as a parameter which is kinda annoying.
I thought about just making a removeFromParentWithFloat( float thisDoesNothing ) function on CCNode just so I can schedule that function, but that seems, dumb.
I’ve been making a CallFunc object with CC_CALLBACK_1, and a DelayTime object in a sequence to get the same effect. But this now takes like 4 lines of code where it once took 1. You run into this issue at all?

//omfg - Haha

How about node->runAction(RemoveSelf::create()); ?? I guess it sounds like you want the delay? I actually don’t think I’ve ever used scheduleOnce? Why not just create a static function and pass the node in instead of modifying CCNode? Do you have a helper or util class? Or just write a global method? I personally don’t like to modify core engine files, if I can help it.

inline void RemoveWithDelay(Node* node, float delay = 1.f) { /* runAction or scheduleOnce */ }

To be honest I use actions quite a bit so writing a sequence is probably something I would do without thinking.

// could one-line, but I don't like wrapping :\
auto delay = DelayTime::create(1.f);
node->runAction(Sequence::create(delay, RemoveSelf::create());

Ugh haha yeah, I started halfway with the actions solution, and halfway with the RemoveFromParent( float notUsed ) method. I’m horrible at this, at least I have options. Nice that cocos has multiple solutions paths built in.

It compiles! Runs, sorta. My Value.asRef solution is not totally working exactly, but I’m not giving up on it just yet.

1 Like