Newer
Older
Atmosphere / stratosphere / tma / source / tma_task_list.cpp
/*
 * Copyright (c) 2018 Atmosphère-NX
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <algorithm>

#include <switch.h>
#include <stratosphere.hpp>
#include "tma_task_list.hpp"

TmaTask *TmaTaskList::GetById(u32 task_id) const {
    for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
        for (auto task : this->tasks[i]) {
            if (task->GetTaskId() == task_id) {
                return task;
            }
        }
    }
    return nullptr;
}

u32 TmaTaskList::GetNumTasks() const {
    std::scoped_lock<HosMutex> lk(this->lock);
    u32 count = 0;
    
    for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
        count += this->tasks[i].size();
    }
    
    return count;
}

u32 TmaTaskList::GetNumSleepingTasks() const {
    std::scoped_lock<HosMutex> lk(this->lock);
    u32 count = 0;
    
    for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
        count += this->sleeping_tasks[i].size();
    }
    
    return count;
}

bool TmaTaskList::SendPacket(bool connected, TmaPacket *packet) {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    TmaTask *target_task = nullptr;
    
    /* This loop both finds a target task, and cleans up finished tasks. */
    for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
        auto it = this->tasks[i].begin();
        while (it != this->tasks[i].end()) {
            auto task = *it;
            switch (task->GetState()) {
                case TmaTaskState::InProgress:
                    it++;
                    if (target_task == nullptr) {
                        if (connected || IsMetaService(task->GetServiceId())) {
                            target_task = nullptr;
                        }
                    }
                    break;
                case TmaTaskState::Complete:
                case TmaTaskState::Canceled:
                    it = this->tasks[i].erase(it);
                    if (task->GetOwnedByTaskList()) {
                        delete task;
                    } else {
                        task->Signal();
                    }
                    break;
                default:
                    /* TODO: Panic to fatal? */
                    std::abort();
            }
        }
    }
    
    if (target_task) {
        /* Setup packet. */
        packet->SetContinuation(true);
        packet->SetServiceId(target_task->GetServiceId());
        packet->SetTaskId(target_task->GetTaskId());
        packet->SetCommand(target_task->GetCommand());
        packet->ClearOffset();
        
        /* Actually handle packet send. */
        target_task->OnSendPacket(packet);
    }
    
    return target_task != nullptr;
}

bool TmaTaskList::ReceivePacket(TmaPacket *packet) {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    auto task = this->GetById(packet->GetTaskId());
    if (task != nullptr) {
        task->OnReceivePacket(packet);
    }
    return task != nullptr;
}

void TmaTaskList::Add(TmaTask *task) {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    this->tasks[task->GetPriority()].push_back(task);
}

void TmaTaskList::Remove(TmaTask *task) {    
    const auto priority = task->GetPriority();
    
    /* Nintendo iterates over all lists instead of just the correct one. */
    /* TODO: Is there actually any reason to do that? */
    auto ind = std::find(this->tasks[priority].begin(), this->tasks[priority].end(), task);
    if (ind != this->tasks[priority].end()) {
        this->tasks[priority].erase(ind);
        return;
    }
    
    /* TODO: Panic to fatal? */
    std::abort();
}

void TmaTaskList::Cancel(u32 task_id) {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    auto task = this->GetById(task_id);
    if (task != nullptr) {
        task->Cancel();
    }
}

void TmaTaskList::CancelAll() {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    for (u32 i = 0 ; i < TmaTask::NumPriorities; i++) {
        for (auto task : this->tasks[i]) {
            task->Cancel();
        }
    }
}

void TmaTaskList::Sleep() {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
        auto it = this->tasks[i].begin();
        while (it != this->tasks[i].end()) {
            auto task = *it;
            if (task->GetSleepAllowed()) {
                it = this->tasks[i].erase(it);
                this->sleeping_tasks[i].push_back(task);
            } else {
                it++;
            }
        }
    }
}

void TmaTaskList::Wake() {
    std::scoped_lock<HosMutex> lk(this->lock);
    
    for (u32 i = 0; i < TmaTask::NumPriorities; i++) {
        auto it = this->sleeping_tasks[i].begin();
        while (it != this->sleeping_tasks[i].end()) {
            auto task = *it;
            it = this->sleeping_tasks[i].erase(it);
            this->tasks[i].push_back(task);
        }
    }
}