-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Graph
class
#172
Add Graph
class
#172
Conversation
Makes current API consistent.
Is it possible to inherit the classes and get the class instead of the GraphVertex and GraphEdge? |
In theory this is possible, and yeah I think it makes sense to add. When you call The current workaround would be to assign your own The idea here is that a
There's no way to override Since you'd like to have your own versions of extends Graph
func _create_vertex() -> GraphVertex:
return RoadIntersection.new()
func _create_edge() -> GraphEdge:
return RoadSegment.new() If we look at |
Some performance notes on the implementation. I'm not particularly going to use this, but I figured I'd post here since it's a bit interesting. I see you use The same goes for vertices and edges, they are heap-allocated full-fledged Objects, when I would probably have made them optional (as metadata?), so by default they are lightweight and maybe not heap-allocated (note, Maps already heap-allocate their elements internally even if they are just pointers), but if you want to go for OOP and let users access vertices as objects or even inherit vertex classes with scripts, it might be a better option, though will be less efficient with large graphs. In other areas like Why use Note about temporary data structures: if a frequently used algorithm needs a vector temporarily, a cheap trick to speed it up is: static thread_local std::vector<Type> s_data; The Godot equivalent would be |
I guess it depends on whether sparse graphs will be used. I'm less experienced with vector tricks... 😢
Yeah, flexibility, ease-of-use have higher priority here. It would also make sense to have different kinds of graphs implemented for undirected, directed, multigraph etc. for even better performance given concrete assumptions, but I'd just like to have something usable at first and not to rely on and hack This also makes it easier to use hierarchical/nested graphs. For large, dense graphs, perhaps we could implement
Note that
Wouldn't this be the case if IDs are used as well? I mean, you must have a key anyway. What do you mean by "same key might appear" problem? Note that even if you attempt to free the vertex/edge object, those will request the graph to erase keys properly and won't remain dangling. But yeah, I'll likely make
Thanks, I'll see how and when this may speed up things. A lot of this is a learning experience for me, however current implementation seems to work okay-ish so far. A general-purpose |
You don't control the value of pointers, but you can control IDs. It can either be auto increment (which will be unique for as long as you need) or even user-defined if that makes some things easier. It is also easier to serialize (to file or network), while serializing a key will be harder if they are objects because the pointer won't carry over to the file or network. Most likely the user will have to maintain a map themselves. |
Yes, that's why I raised the question, because it's an implementation detail as of now. But as you say, introducing IDs would be needed for serialization purposes, so I'm likely going to refactor the implementation to enable easier serialization. About the undo/redo part, this marginally reminded me of #162 where I keep the history of draw commands. Perhaps using IDs could help with visualizing/debugging algorithms. 🤔 |
For some reason, this works 20% slower compared to previous implementation. Perhaps this is due to object pointer hashing.
I've done some major refactor to use Still using lists for edges, but I really wonder what kind of performance impact this has. I'll leave this for the final touch and/or when I feel motivated enough to do this sort of optimization with vectors... 😭 Unfortunately, I'm not sure whether I've noticed major performance improvements. But I think the current state is certainly an improvement structurally. I've only referred to Again, for reference, I took inspiration from https://networkx.org/ API. |
Because `GraphVertex` could be eventually scripted, like `RoadIntersection`, so lets be more agnostic.
@@ -337,16 +337,16 @@ void GraphEdge::_notification(int p_what) { | |||
} | |||
|
|||
void GraphEdge::_bind_methods() { | |||
ClassDB::bind_method(D_METHOD("get_vertex_a"), &GraphEdge::get_vertex_a); | |||
ClassDB::bind_method(D_METHOD("get_vertex_b"), &GraphEdge::get_vertex_b); | |||
ClassDB::bind_method(D_METHOD("get_a"), &GraphEdge::get_a); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe start/end? might be good? or maybe from/to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from/to
implies direction, which is not always the case for an edge (associative edges), so this may lead to confusion. You can determine whether an edge has a direction by calling is_directed()
For associative edges, the following statements are equivalent:
graph.has_edge(a, b)
graph.has_edge(b, a)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems a bit weird with a, b, could we go for any other name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without implying direction, first/second
comes to mind. This is how edges are accessed internally (via Pair
keys, which also has first/second
members).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
get_first() and get_last() probably?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
last
sounds like there's a collection to choose from, however it's shorter. Yet a/b
is even shorter. 😛
If you look at Godot naming, the SegmentShape2D
also has a
and b
to denote endpoints.
Joints have both node_a
and node_b
properties.
That's why I prefer a/b
because it's the naming convention Godot currently uses. I'm open to other opinions, though.
Using `_create_vertex()` and `_create_edge()`. Custom scripted vertices and edges will be instantiated provided that a user returns new instances via script.
You can also implement the AStar algorithm if possible. |
btw, could implement a way so we can pass extra info to the constructor of our own classes.
and then the value of the bind args could be passed like this.
it could also be a array, just pass in whatever you get in the bind args haha. Add a way to fetch the internal ids too, would be really helpful! Also how do you differentiate the multi-edged graph with ids (not with objects)? |
I think this could be added by implementing
I think we have some misunderstanding here with virtual callback vs signal callback. The point of those virtual callbacks is to override vertex/edge instantiation. Since you're extending class_name RoadIntersection extends GraphVertex
func _init(p_position, p_road_net):
value = {}
value.road_net = p_road_net
value.position = p_position But when you call Notice that you can use From what I can tell, it appears to me that you want to have the
What for? Can you show where having access to internal IDs would be helpful?
IDs are generated starting from
Is comparing objects impossible in your case? I'm asking because current architecture is object-oriented, and mostly everything was designed that way in mind. I'd just like to understand what you struggle with. See also my previous post: #172 (comment) |
This is mostly a convenience feature, which allows to simply building up the graph initially.
Same as in `GraphVertex`. Depends on whether you treat the graph as vertex-centric or edge-centric.
how would you pass the p_road_net and p_position?
I'm sorry, it was road_info (RoadNetworkInfo (for rendering system and ai stuff)).
My Graph actually uses ID based system, and ids are generated based on the position, there's a |
Here's another way: # Add
var intersection = graph.add_vertex({id=10, position = Vector2(x, y), road_net = info})
# Access
var id = intersection.value.id
var position: Vector2 = intersection.value.position
var road_net: Dictionary = intersection.value.road_net You can bypass all of this and simply use The fact that the internal implementation uses IDs does not mean that you should rely on it or even assume that it exists.
I think that actually calling I thought about using IDs as well initially, but see godotengine/godot-proposals#3848 (comment). Otherwise I'd be using So again, the priority here is flexibility due to needed generality. Yet it's fast enough. |
The first allows for iterating `Graph` vertices, second - `GraphVertex` neighbors.
Consistent with `GraphVertex.get_neighbors()`, `Node.get_children()` and alike.
I think the core implementation is there. I'd like to merge this to let other people to try it out, and let https://github.com/goostengine/goost-fuzzer find the bugs that perhaps I couldn't find myself. There will likely be some compatibility changes. See example project at https://github.com/goostengine/goost-examples/tree/gd3/core/graph. Thanks for feedback! |
Could you please add ability to fetch all edges a vertex is connected? Something like |
@sairam4123 makes sense, but I'll probably need to refactor how
But do note that it's possible to do this via script at the moment. |
Thanks! I was thinking about it too, it should be really helpful for me! |
Closes godotengine/godot-proposals#3848.
Intended to implement a mixed graph data structure. Allows to add both associative (undirected) and directed edges, with multiple edges between a pair of vertices, and self-loops.
(annotations are optional)
To-do