Bug? GraphUpdateObject/Scene setTag is an int, not uint

As I understand it, all tags are considered unsigned ints in order to use all 32 bits as flags in the mask. I noticed when trying to set graph tags using GraphUpdateObject that the setTag property was just an int. This appears to also be the case for GraphUpdateScene.

Affected classes:
https://arongranberg.com/astar/docs/graphupdateobject.html - setTag
https://arongranberg.com/astar/docs/graphupdatescene.html - setTag, setTagInvert

Repro code:

uint myTags = 1 << 0 | 1 << 10; // Set the 0th and 10th tags
GraphUpdateObject guo = new GraphUpdateObject() {
    modifyTag = true,
    setTag = myTags,
};

Or… maybe I’m missing something?

I’m also seeing that whatever I place in setTag is set mod(32)… so something like this is occurring:

GraphNode node = MyGridGraph.GetNode(0, 0);
GraphUpdateObject guo = new GraphUpdateObject() {
    modifyTag = true,
    setTag = 33,
};
guo.WillUpdateNode(node);
guo.Apply(node);

Debug.Log(node.Tag); // Outputs "1"

Is it expecting a tag index and not a tag value? I tried using it like setTag = 1 << 10 and that just set the tag to “0” since 1024%32 is 0. Since the node.Tag uint appears to just be an index from 0-31, I can’t actually use it like a bitmask to hold multiple tags at once. I must be missing something.

I’ve looked all throughout https://arongranberg.com/astar/docs/bitmasks.html and https://arongranberg.com/astar/docs/tags.html, but not seeing anything concrete about how to actually use them. Some example code would be extremely helpful right about now! Thank you!

Hi

Ah, yeah that does seem to be a mistake by me. It will work well with an int as well though (even the 32nd bit) so all features should work.

It is a tag index indeed, I am not quite sure what you mean by tag value? A node can only have a single tag, where a tag is a number between 0 and 31 (inclusive). However a path can be configured with a bitmask so that it is allowed to traverse a specific set of tags. Trying to set a tag value above 31 on a node is invalid, however in practice it will set it to the value mod 32 (for implementation performance reasons).

int tag = 10; // Set the 0th and 10th tags
GraphUpdateObject guo = new GraphUpdateObject() {
    modifyTag = true,
    setTag = tag,
};

...

// Prevent the paths created by the Seeker to traverse any nodes with tags that are not either tag 5 or tag 10 (set above).
seeker.traversableTags = 1 << tag | 1 << 5;

Wild… Yeah, I was massively confused by when tags were supposed to be used as indices, and when they were supposed to be used as a bitmask. I really thought that tags-to-nodes were many-to-one, and were used entirely as their bitshifted values (not the 0-31 index).

Please forgive me as I venture deep into feature request territory :wink:

For example, if I had a bunch of tags stored in a Tags class:

public static class Tags {
    public static readonly uint Dirt = 1 << 0;
    public static readonly uint Wood = 1 << 3;
    public static readonly uint Treasure = 1 << 9;
    public static readonly uint OnFire = 1 << 12;
    // ... etc ...
}

With this setup, none of the pathfinding interfaces would use indices at all - they’d all communicate with bitmasks. Both Dirt and Wood could have Treasure on it, but only Wood could be on fire. The Treasure and OnFire tags would be a target for GetNearest() or PathUtilities.BFS(), while Dirt and Wood would be for traversability. Imagine upgrading a dirt tile to wood, but it’s right next to lava so it catches on fire:

GraphUpdateObject guo = new GraphUpdateObject() {
    modifyTag = true,
    removeTags = Tags.Dirt, // (1 << 0)
    addTags = Tags.Wood | Tags.OnFire, // ( 1 << 3 | 1 << 12)
};
guo.Apply(node);

Something like this would be particularly useful to me right now, as I have multiple terrain types (sand, dirt, grass) and multiple resource types (wheat, wood, stone)… but every resource can potentially exist on any terrain type. The first set of tags tells my agents what they can walk on, while the second set tell them where they should go (using GetNearest() + NNConstraint and/or PathUtilities.BFS() + tagMask.

I’m not actually sure how something like PathUtilities.BFS() is useful right now… I’m trying to use it to find the nearest node in a given Area with a certain resource type on it: PathUtilities.BFS(myNode, 15, (1 << TreeTag))… but this will immediately fail because the ground the agent is standing on isn’t a Tree, so the root node is eliminated and there’s nothing left to do. But if I give it (1 << GroundTag) | (1 << TreeTag ) it’ll return everything inside the radius whether or not it has a tree on it…

Anyway, knowing that nodes and tags have a 1-to-1 relationship, and that getters/setters are indices while masks are bitshifted by the index ( 1 << tagIndex ) will get me pretty far… I think I can get that to work?

Thanks as always for your help!

I would suggest that your tags class contains the tag indices instead, like:

public static class Tags {
    public static readonly uint Dirt = 0;
    public static readonly uint Wood = 3;
    public static readonly uint Treasure = 9;
    public static readonly uint OnFire = 12;
    // ... etc ...
}

I think you should be able to find the nearest node with a specific tag using something like:

class TagEndingCondition : PathEndingCondition {
	int tag;
	public TagEndingCondition(Path path, int tag) : base(path) {
		this.tag = tag;
	}
}


XPath path = XPath.Construct(originPoint, originPoint);
path.endingCondition = new TagEndingCondition(path, someTag);
path.heuristic = Heuristic.None; // Search radially outwards from the start (using a Dijkstra)
seeker.StartPath(path);

Interesting! I’ll definitely take a look at your suggestion. I was struggling to find a “find the nearest node in this area with X tag” function.

I was just giving an example of how my intuition thought the tag system worked (many-to-one, stored as uint bit flags). My actual Tags class does indeed look like the one you suggested :slight_smile: Thanks once more for being so supportive on the forums! I’ll be playing with this tonight for sure!

1 Like