<commit_before>﻿using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(Ship))]
public class AITaskFollower : MonoBehaviour, ISerializationCallbackReceiver
{
    /* create and initialize an AITaskFollower (should do everything
     Start() does so we can use it immediately) */
    public static AITaskFollower AddToShip(Ship ship)
    {
        var ai = ship.gameObject.AddComponent<AITaskFollower>();
        ai.Ship = ship;

        return ai;
    }

    private LinkedList<AITask> tasks;
    
    [SerializeField]
    private AITask[] serializedTasks;
    
    public Ship Ship { get; private set; }

    public bool Idle { get { return tasks.Count == 0; } }

    public AITask CurrentTask { get { return tasks.Count == 0 ? null : tasks.Last.Value; } }

    public void OnBeforeSerialize()
    {
        if (tasks != null)
        {
            serializedTasks = new AITask[tasks.Count];
            tasks.CopyTo(serializedTasks, 0);
        }
        else
        {
            serializedTasks = new AITask[0];
        }
    }

    public void OnAfterDeserialize()
    {
        tasks = new LinkedList<AITask>(serializedTasks);
    }

    private void PrepareNewTask(AITask task)
    {
        Debug.Assert(Ship, "AI must have a ship reference before assigning tasks");

        task.CheckRequiredConstraints();
        task.Status = AITask.TaskStatus.NEW;
        task.TaskFollower = this;
    }
    
    /// <summary>
    /// adds a task to the end of the task list, to be done right now
    /// </summary>
    /// <param name="task"></param>
    public void AssignTask(AITask task)
    {
        PrepareNewTask(task);
        tasks.AddLast(task);
    }

    /// <summary>
    /// Adds a task to the beginning of the task list, to be done when all other tasks
    /// are finished
    /// </summary>
    public void QueueTask(AITask task)
    {
        PrepareNewTask(task);
        tasks.AddFirst(task);
    }

    public void ClearTasks()
    {
        LinkedListNode<AITask> last;
        while ((last = tasks.Last) != null)
        {
            last.Value.Status = AITask.TaskStatus.FINISHED;
            last.Value.End();
            Destroy(last.Value);
            tasks.RemoveLast();
        }
    }
    
    private void OnCollisionStay(Collision collision)
    {
        var task = tasks.First;
        if (task != null)
        {
            task.Value.OnCollided(collision);
        }
    }

    /// <summary>
    /// cancel a task and every task above it in the stack
    /// </summary>
    public void CancelTask(AITask cancelled)
    {
        Debug.Assert(!!cancelled, "cancelled task must exist");
        Debug.Assert(tasks.Contains(cancelled), "can't cancel a task we don't have");

        var last = tasks.Last;

        do
        {
            last.Value.Status = AITask.TaskStatus.FINISHED;
            last.Value.End();
            Destroy(last.Value);
            tasks.RemoveLast();
        }
        while ((last = tasks.Last) != null);
    }

    void Awake()
    {
        tasks = new LinkedList<AITask>();
        Ship = GetComponent<Ship>();
    }
    
    void Update()
    {
        //ai doesn't run while jumping
        if (Ship.JumpTarget)
        {
            return;
        }

        while (true)
        {
            /* a task execution can add new tasks itself, but during an update we
            skip those at the end of the list that are marked NEW */
            LinkedListNode<AITask> nextTaskNode = tasks.Last;
            while(nextTaskNode != null && nextTaskNode.Value.Status == AITask.TaskStatus.NEW)
            {
                nextTaskNode = nextTaskNode.Previous;
            }

            if (nextTaskNode == null)
            {
                //no tasks remaining that aren't NEW
                break;
            }

            var nextTask = nextTaskNode.Value;

            if (nextTask.Status == AITask.TaskStatus.NOT_STARTED)
            {
                nextTask.Status = AITask.TaskStatus.IN_PROGRESS;
                nextTask.Begin();
            }

            nextTask.Update();

            if (nextTask.Done)
            {
                nextTask.Status = AITask.TaskStatus.FINISHED;
                nextTask.End();
                Destroy(nextTask);
                tasks.Remove(nextTaskNode);
            }
            else
            {
                /* stop executing tasks as soon as there's at least one
                non-NEW task that isn't DONE after one update */
                break;
            }
        }

        /* change status of tasks added during this update so they're not
        skipped next update */
        foreach (var task in tasks)
        {
            if (task.Status == AITask.TaskStatus.NEW)
            {
                task.Status = AITask.TaskStatus.NOT_STARTED;
            }
        }
    }
}<commit_msg>Minor TaskFollower fixes<commit_after>﻿using UnityEngine;
using System.Collections.Generic;

[RequireComponent(typeof(Ship))]
public class AITaskFollower : MonoBehaviour, ISerializationCallbackReceiver
{
    /* create and initialize an AITaskFollower (should do everything
     Start() does so we can use it immediately) */
    public static AITaskFollower AddToShip(Ship ship)
    {
        var ai = ship.gameObject.AddComponent<AITaskFollower>();
        ai.Ship = ship;

        return ai;
    }

    private LinkedList<AITask> tasks;
    
    [SerializeField]
    private AITask[] serializedTasks;
    
    public Ship Ship { get; private set; }

    public bool Idle { get { return tasks.Count == 0; } }

    public AITask CurrentTask { get { return tasks.Count == 0 ? null : tasks.Last.Value; } }

    public void OnBeforeSerialize()
    {
        if (tasks != null)
        {
            serializedTasks = new AITask[tasks.Count];
            tasks.CopyTo(serializedTasks, 0);
        }
        else
        {
            serializedTasks = new AITask[0];
        }
    }

    public void OnAfterDeserialize()
    {
        tasks = new LinkedList<AITask>(serializedTasks);
    }

    private void PrepareNewTask(AITask task)
    {
        Debug.Assert(Ship, "AI must have a ship reference before assigning tasks");

        task.CheckRequiredConstraints();
        task.Status = AITask.TaskStatus.NEW;
        task.TaskFollower = this;
    }
    
    /// <summary>
    /// adds a task to the end of the task list, to be done right now
    /// </summary>
    /// <param name="task"></param>
    public void AssignTask(AITask task)
    {
        Debug.Assert(!tasks.Contains(task), "shouldn't assign same task twice");

        PrepareNewTask(task);
        tasks.AddLast(task);
    }

    /// <summary>
    /// Adds a task to the beginning of the task list, to be done when all other tasks
    /// are finished
    /// </summary>
    public void QueueTask(AITask task)
    {
        Debug.Assert(!tasks.Contains(task), "shouldn't assign same task twice");

        PrepareNewTask(task);
        tasks.AddFirst(task);
    }

    public void ClearTasks()
    {
        LinkedListNode<AITask> last;
        while ((last = tasks.Last) != null)
        {
            last.Value.Status = AITask.TaskStatus.FINISHED;
            last.Value.End();
            Destroy(last.Value);
            tasks.RemoveLast();
        }
    }
    
    private void OnCollisionStay(Collision collision)
    {
        var task = tasks.First;
        if (task != null)
        {
            task.Value.OnCollided(collision);
        }
    }

    /// <summary>
    /// cancel a task and every task above it in the stack
    /// </summary>
    public void CancelTask(AITask cancelled)
    {
        Debug.Assert(!!cancelled, "cancelled task must exist");
        Debug.Assert(tasks.Contains(cancelled), "can't cancel a task we don't have");

        var last = tasks.Last;

        do
        {
            last.Value.Status = AITask.TaskStatus.FINISHED;
            last.Value.End();
            Destroy(last.Value);
            tasks.RemoveLast();
        }
        while ((last = tasks.Last) != null);
    }

    void Awake()
    {
        tasks = new LinkedList<AITask>();
        Ship = GetComponent<Ship>();
    }
    
    void Update()
    {
        //ai doesn't run while jumping
        if (Ship.JumpTarget)
        {
            return;
        }

        while (true)
        {
            /* a task execution can add new tasks itself, but during an update we
            skip those at the end of the list that are marked NEW */
            LinkedListNode<AITask> nextTaskNode = tasks.Last;
            while(nextTaskNode != null && nextTaskNode.Value.Status == AITask.TaskStatus.NEW)
            {
                nextTaskNode = nextTaskNode.Previous;
            }

            if (nextTaskNode == null)
            {
                //no tasks remaining that aren't NEW
                break;
            }

            var nextTask = nextTaskNode.Value;

            if (nextTask.Status == AITask.TaskStatus.NOT_STARTED)
            {
                nextTask.Status = AITask.TaskStatus.IN_PROGRESS;
                nextTask.Begin();
            }

            nextTask.Update();

            if (nextTask.Done)
            {
                /* ship controls are stateful so make sure we reset them after
                 finishing a task */
                Ship.ResetControls();

                nextTask.Status = AITask.TaskStatus.FINISHED;
                nextTask.End();
                Destroy(nextTask);
                tasks.Remove(nextTaskNode);
            }
            else
            {
                /* stop executing tasks as soon as there's at least one
                non-NEW task that isn't DONE after one update */
                break;
            }
        }

        /* change status of tasks added during this update so they're not
        skipped next update */
        foreach (var task in tasks)
        {
            if (task.Status == AITask.TaskStatus.NEW)
            {
                task.Status = AITask.TaskStatus.NOT_STARTED;
            }
        }
    }
}
{'fraction_non_alphanumeric': 0.09755126418475014, 'fraction_numerical': 0.0007963368504877563, 'mean_word_length': 1.6239226952206842, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4019233', 'n_tokens_mistral': 2853, 'n_tokens_neox': 2754, 'n_words': 731}