model/model_viz: use OrderedTree[MConcern]
[nit.git] / lib / a_star.nit
index 97925ff..7b7ce02 100644 (file)
@@ -14,9 +14,9 @@
 # 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
 
@@ -87,14 +91,14 @@ class 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
 
@@ -116,7 +120,7 @@ class Node
                loop
                        var frontier_node: nullable N = null
 
-                       var bucket_searched: Int = 0
+                       var bucket_searched = 0
 
                        # find next valid node in frontier/buckets
                        loop
@@ -143,11 +147,12 @@ class Node
                        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
@@ -182,10 +187,32 @@ class Node
                        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
 
@@ -209,6 +236,8 @@ end
 
 # General graph
 class Graph[N: Node, L: Link]
+       super Serializable
+
        # Nodes in this graph
        var nodes: Set[N] = new HashSet[N]
 
@@ -235,15 +264,35 @@ class Graph[N: Node, L: Link]
 
        # 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
@@ -251,7 +300,7 @@ class Path[N]
        # 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
@@ -267,7 +316,9 @@ class Path[N]
 end
 
 # Context related to an evocation of pathfinding
-class PathContext
+abstract class PathContext
+       serialize
+
        # Type of the nodes in `graph`
        type N: Node
 
@@ -301,6 +352,7 @@ end
 # 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
@@ -312,6 +364,7 @@ end
 # A `PathContext` for graphs with `WeightedLink`
 class WeightedPathContext
        super PathContext
+       serialize
 
        redef type L: WeightedLink
 
@@ -327,7 +380,7 @@ class WeightedPathContext
                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
@@ -340,13 +393,16 @@ end
 # 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