# See the License for the specific language governing permissions and
# limitations under the License.
-# Services related to pathfinding of graphs using A*
-# A single graph may have different properties according to the `PathContext` used
+# A* pathfinding in graphs
#
+# A single graph may have different properties according to the `PathContext` used
#
# Usage:
#
# ~~~
module a_star
+import serialization
+
# General graph node
class Node
+ super Serializable
+
# Type of the others nodes in the `graph`
type N: Node
private var open: Bool = false
# Main functionnality, returns path from `self` to `dest`
- fun path_to(dest: N, max_cost: Int, context: PathContext): nullable Path[N]
+ fun path_to(dest: N, max_cost: Int, context: PathContext): nullable AStarPath[N]
do
return path_to_alts(dest, max_cost, context, null)
end
# Find a path to a possible `destination` or a node accepted by `alt_targets`
fun path_to_alts(destination: nullable N, max_cost: Int, context: PathContext,
- alt_targets: nullable TargetCondition[N]): nullable Path[N]
+ alt_targets: nullable TargetCondition[N]): nullable AStarPath[N]
do
var cost = 0
loop
var frontier_node: nullable N = null
- var bucket_searched: Int = 0
+ var bucket_searched = 0
# find next valid node in frontier/buckets
loop
else if frontier_node == destination or
(alt_targets != null and alt_targets.accept(frontier_node)) then
- var path = new Path[N](cost)
+ var path = new AStarPath[N](cost)
while frontier_node != self do
path.nodes.unshift(frontier_node)
- frontier_node = frontier_node.best_source.as(not null)
+ frontier_node = frontier_node.best_source
+ assert frontier_node != null
end
return path
end
end
end
+
+ # We customize the serialization process to avoid problems with recursive
+ # serialization engines. These engines, such as `JsonSerializer`,
+ # are at danger to serialize the graph as a very deep tree.
+ # With a large graph it can cause a stack overflow.
+ #
+ # Instead, we serialize the nodes first and then the links.
+ redef fun core_serialize_to(serializer)
+ do
+ serializer.serialize_attribute("graph", graph)
+ end
+
+ redef init from_deserializer(deserializer)
+ do
+ deserializer.notify_of_creation self
+
+ var graph = deserializer.deserialize_attribute("graph")
+ assert graph isa Graph[N, Link]
+ self.graph = graph
+ end
end
# Link between two nodes and associated to a graph
class Link
+ serialize
+
# Type of the nodes in `graph`
type N: Node
# General graph
class Graph[N: Node, L: Link]
+ super Serializable
+
# Nodes in this graph
var nodes: Set[N] = new HashSet[N]
# Used to check if nodes have been searched in one pathfinding
private var pathfinding_current_evocation: Int = 0
+
+ redef fun core_serialize_to(serializer)
+ do
+ serializer.serialize_attribute("nodes", nodes)
+ serializer.serialize_attribute("links", links)
+ end
+
+ redef init from_deserializer(deserializer)
+ do
+ deserializer.notify_of_creation self
+
+ var nodes = deserializer.deserialize_attribute("nodes")
+ assert nodes isa HashSet[N]
+ self.nodes = nodes
+
+ var links = deserializer.deserialize_attribute("links")
+ assert links isa HashSet[L]
+ for link in links do add_link link
+ end
end
# Result from path finding and a walkable path
-class Path[N]
+class AStarPath[N]
+ serialize
- # The total cost of this path
+ # Total cost of this path
var total_cost: Int
- # The list of nodes composing this path
+ # Nodes composing this path
var nodes = new List[N]
private var at: Int = 0
# Step on the path and get the next node to travel
fun step: N
do
- assert nodes.length >= at else print "a_star::Path::step failed, is at_end_of_path"
+ assert nodes.length >= at else print "a_star::AStarPath::step failed, is at_end_of_path"
var s = nodes[at]
at += 1
end
# Context related to an evocation of pathfinding
-class PathContext
+abstract class PathContext
+ serialize
+
# Type of the nodes in `graph`
type N: Node
# Warning: A* is not optimize for such a case
class ConstantPathContext
super PathContext
+ serialize
redef fun worst_cost do return 1
redef fun cost(l) do return 1
# A `PathContext` for graphs with `WeightedLink`
class WeightedPathContext
super PathContext
+ serialize
redef type L: WeightedLink
self.worst_cost = worst_cost
end
- redef var worst_cost: Int is noinit
+ redef var worst_cost is noinit
redef fun cost(l) do
return l.weight
# A `Link` with a `weight`
class WeightedLink
super Link
+ serialize
# The `weight`, or cost, of this link
var weight: Int
end
# Advanced path conditions with customizable accept states
-class TargetCondition[N: Node]
+abstract class TargetCondition[N: Node]
+ serialize
+
# Should the pathfinding accept `node` as a goal?
fun accept(node: N): Bool is abstract