public override void Visitor(ProbabilisticGameNode cloned, GameTree <GameNode> tree, QueueActionEventArgs e) { // If the action queue is empty, we have reached a leaf node game state // so compare it for equality with other final game states if (cloned.Game.ActionQueue.IsEmpty) { if (!cloned.Game.EquivalentTo(e.Game)) { tree.LeafNodeCount++; // This will cause the game to be discarded if its fuzzy hash matches any other final game state // TODO: Optimize to use TLS and avoid spinlocks lock (uniqueGames) { if (!uniqueGames.ContainsKey(cloned.Game)) { uniqueGames.Add(cloned.Game, cloned.Probability); #if _TREE_DEBUG DebugLog.WriteLine("UNIQUE GAME FOUND ({0}) - Hash: {1:x8}", uniqueGames.Count, cloned.Game.FuzzyGameHash); DebugLog.WriteLine("{0:S}", cloned.Game); #endif } else { uniqueGames[cloned.Game] += cloned.Probability; #if _TREE_DEBUG DebugLog.WriteLine("DUPLICATE GAME FOUND - Hash: {0:x8}", cloned.Game.FuzzyGameHash); #endif } } } } }
public override void Visitor(ProbabilisticGameNode cloned, GameTree <GameNode> tree, QueueActionEventArgs e) { // If the action queue is empty, we have reached a leaf node game state // TODO: Optimize to use TLS and avoid spinlocks if (cloned.Game.ActionQueue.IsEmpty) { tree.LeafNodeCount++; lock (leafNodeGames) { leafNodeGames.Add(cloned); } } }
// When an in-game action completes, check if the game state has changed // Some actions (like selectors) won't cause the game state to change, // so we continue running these until a game state change occurs public override void PostAction(ActionQueue q, GameTree <GameNode> t, QueueActionEventArgs e) { // This game will be on the same thread as the calling task in parallel mode if it hasn't been cloned // If it has been cloned, it may be on a different thread if (e.Game.Changed) { e.Game.Changed = false; // If the action queue is empty, we have reached a leaf node game state // so compare it for equality with other final game states if (e.Game.ActionQueue.IsEmpty) { t.LeafNodeCount++; // This will cause the game to be discarded if its fuzzy hash matches any other final game state if (!tlsUniqueGames.Value.ContainsKey(e.Game)) { tlsUniqueGames.Value.Add(e.Game, e.UserData as ProbabilisticGameNode); #if _TREE_DEBUG DebugLog.WriteLine("UNIQUE GAME FOUND ({0}) - Hash: {1:x8}", uniqueGames.Count + tlsUniqueGames.Value.Count, e.Game.FuzzyGameHash); DebugLog.WriteLine("{0:S}", e.Game); #endif } else { tlsUniqueGames.Value[e.Game].Probability += ((ProbabilisticGameNode)e.UserData).Probability; #if _TREE_DEBUG DebugLog.WriteLine("DUPLICATE GAME FOUND - Hash: {0:x8}", e.Game.FuzzyGameHash); #endif } } else { // The game state has changed but there are more actions to do // (which may or may not involve further cloning) so add it to the search queue #if _TREE_DEBUG DebugLog.WriteLine("QUEUEING GAME " + e.Game.GameId + " FOR NEXT SEARCH"); #endif if (!tlsSearchQueue.Value.ContainsKey(e.Game)) { tlsSearchQueue.Value.Add(e.Game, e.UserData as ProbabilisticGameNode); } else { tlsSearchQueue.Value[e.Game].Probability += ((ProbabilisticGameNode)e.UserData).Probability; } } #if _TREE_DEBUG DebugLog.WriteLine(""); #endif e.Cancel = true; } }
// This is the entry point after the root node has been pushed into the queue // and the first change to the game has occurred public override async Task PostProcess(GameTree <GameNode> t) { // Breadth-first processing loop do { // Merge the TLS lists into the main lists foreach (var sq in tlsSearchQueue.Values) { foreach (var qi in sq) { if (!searchQueue.ContainsKey(qi.Key)) { searchQueue.Add(qi.Key, qi.Value); } else { searchQueue[qi.Key].Probability += qi.Value.Probability; } } } foreach (var ug in tlsUniqueGames.Values) { foreach (var qi in ug) { if (!uniqueGames.ContainsKey(qi.Key)) { uniqueGames.Add(qi.Key, qi.Value); } else { uniqueGames[qi.Key].Probability += qi.Value.Probability; } } } #if _TREE_DEBUG DebugLog.WriteLine("QUEUE SIZE: " + searchQueue.Count); #endif // Wipe the TLS lists tlsSearchQueue = new ThreadLocal <Dictionary <Game, ProbabilisticGameNode> >(() => new Dictionary <Game, ProbabilisticGameNode>(new FuzzyGameComparer()), trackAllValues: true); tlsUniqueGames = new ThreadLocal <Dictionary <Game, ProbabilisticGameNode> >(() => new Dictionary <Game, ProbabilisticGameNode>(new FuzzyGameComparer()), trackAllValues: true); // Quit if we have processed all nodes and none of them have children (all leaf nodes) if (searchQueue.Count == 0) { break; } // Copy the search queue and clear the current one; it will be refilled var nextQueue = new Dictionary <Game, ProbabilisticGameNode>(searchQueue); searchQueue.Clear(); // Only parallelize if there are sufficient nodes to do so if (nextQueue.Count >= MinNodesToParallelize && ((RandomOutcomeSearch)t).Parallel && MaxDegreesOfParallelism > 1) { // Process each game's action queue until it is interrupted by OnAction above await Task.WhenAll( // Split search queue into MaxDegreesOfParallelism partitions from partition in Partitioner.Create(nextQueue).GetPartitions(MaxDegreesOfParallelism) // Run each partition in its own task select Task.Run(async delegate { #if _TREE_DEBUG var count = 0; DebugLog.WriteLine("Start partition run with " + MaxDegreesOfParallelism + " degrees of parallelism"); #endif using (partition) while (partition.MoveNext()) { // Process each node var kv = partition.Current; await kv.Key.ActionQueue.ProcessAllAsync(kv.Value); #if _TREE_DEBUG count++; #endif } #if _TREE_DEBUG DebugLog.WriteLine("End run with partition size {0}", count); #endif })); #if _TREE_DEBUG DebugLog.WriteLine("======================="); DebugLog.WriteLine("CLONES SO FAR: " + t.NodeCount + " / " + t.LeafNodeCount); DebugLog.WriteLine("UNIQUE GAMES SO FAR: " + uniqueGames.Count); DebugLog.WriteLine("NEW QUEUE SIZE: " + searchQueue.Count + "\r\n"); #endif } else { #if _TREE_DEBUG DebugLog.WriteLine("Start single-threaded run"); #endif // Process each node in the search queue sequentially foreach (var kv in nextQueue) { await kv.Key.ActionQueue.ProcessAllAsync(kv.Value); } #if _TREE_DEBUG DebugLog.WriteLine("End single-threaded run"); #endif } } while (true); }
public virtual void Visitor(ProbabilisticGameNode cloned, GameTree <GameNode> tree, QueueActionEventArgs e) { }
public virtual Task PostProcess(GameTree <GameNode> tree) { return(Task.FromResult(0)); }
public virtual void PostAction(ActionQueue q, GameTree <GameNode> tree, QueueActionEventArgs e) { }