Verified Commit 04790ea7 authored by Alberto Miranda's avatar Alberto Miranda ♨️
Browse files

Replace api::convert() with conversion operators

Also, remove related conversion source files
parent 5a64ebea
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@
add_library(_api_types STATIC)

target_sources(_api_types PUBLIC scord/types.h scord/types.hpp PRIVATE
  types.cpp convert.hpp convert.cpp scord/internal_types.hpp errors.c)
  types.c types.cpp scord/internal_types.hpp errors.c)

target_include_directories(_api_types PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})

src/common/api/convert.cpp

deleted100644 → 0
+0 −337
Original line number Diff line number Diff line
/******************************************************************************
 * Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
 *
 * This software was partially supported by the EuroHPC-funded project ADMIRE
 *   (Project ID: 956748, https://www.admire-eurohpc.eu).
 *
 * This file is part of scord.
 *
 * scord is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * scord is distributed in the hope that 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 scord.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *****************************************************************************/

#include <algorithm>
#include <net/proto/rpc_types.h>
#include "convert.hpp"

// forward declarations
ADM_job_t
ADM_job_create(uint64_t id, uint64_t slurm_id);

ADM_transfer_t
ADM_transfer_create(uint64_t id);

namespace {

scord::api::managed_ctype_array<ADM_node_t>
as_ctype_array(const std::vector<scord::node>& nodes) {

    std::vector<ADM_node_t> tmp;

    std::transform(nodes.cbegin(), nodes.cend(), std::back_inserter(tmp),
                   [](const scord::node& n) {
                       return ADM_node_create(n.hostname().c_str());
                   });

    return scord::api::managed_ctype_array<ADM_node_t>{std::move(tmp)};
}

scord::api::managed_ctype_array<ADM_dataset_t>
as_ctype_array(const std::vector<scord::dataset>& datasets) {

    std::vector<ADM_dataset_t> tmp;

    std::transform(datasets.cbegin(), datasets.cend(), std::back_inserter(tmp),
                   [](const scord::dataset& d) {
                       return ADM_dataset_create(d.id().c_str());
                   });

    return scord::api::managed_ctype_array<ADM_dataset_t>{std::move(tmp)};
}

} // namespace

namespace scord::api {

managed_ctype<ADM_node_t>
convert(const node& node) {
    return managed_ctype<ADM_node_t>(ADM_node_create(node.hostname().c_str()));
}

managed_ctype<ADM_adhoc_resources_t>
convert(const adhoc_storage::resources& res) {

    auto managed_nodes = as_ctype_array(res.nodes());

    ADM_adhoc_resources_t c_res = ADM_adhoc_resources_create(
            managed_nodes.data(), managed_nodes.size());

    return managed_ctype<ADM_adhoc_resources_t>{c_res,
                                                std::move(managed_nodes)};
}

managed_ctype<ADM_adhoc_context_t>
convert(const adhoc_storage::ctx& ctx) {

    auto managed_adhoc_resources = convert(ctx.resources());

    return managed_ctype<ADM_adhoc_context_t>{
            ADM_adhoc_context_create(
                    static_cast<ADM_adhoc_mode_t>(ctx.exec_mode()),
                    static_cast<ADM_adhoc_access_t>(ctx.access_type()),
                    managed_adhoc_resources.get(), ctx.walltime(),
                    ctx.should_flush()),
            std::move(managed_adhoc_resources)};
}

managed_ctype<ADM_adhoc_storage_t>
convert(const std::optional<scord::adhoc_storage>& adhoc_storage) {

    if(!adhoc_storage) {
        return managed_ctype<ADM_adhoc_storage_t>{};
    }

    return convert(adhoc_storage.value());
}

managed_ctype<ADM_adhoc_storage_t>
convert(const scord::adhoc_storage& st) {

    auto managed_ctx = convert(st.context());
    ADM_adhoc_storage_t c_st = ADM_adhoc_storage_create(
            st.name().c_str(), static_cast<ADM_adhoc_storage_type_t>(st.type()),
            st.id(), managed_ctx.get());

    return managed_ctype<ADM_adhoc_storage_t>{c_st, std::move(managed_ctx)};
}

managed_ctype<ADM_pfs_context_t>
convert(const pfs_storage::ctx& ctx) {
    return managed_ctype<ADM_pfs_context_t>{
            ADM_pfs_context_create(ctx.mount_point().c_str())};
}

managed_ctype<ADM_pfs_storage_t>
convert(const std::optional<scord::pfs_storage>& pfs_storage) {

    if(!pfs_storage) {
        return managed_ctype<ADM_pfs_storage_t>{};
    }

    return convert(pfs_storage.value());
}

managed_ctype<ADM_pfs_storage_t>
convert(const scord::pfs_storage& st) {

    auto managed_ctx = convert(st.context());
    ADM_pfs_storage_t c_st = ADM_pfs_storage_create(
            st.name().c_str(), static_cast<ADM_pfs_storage_type_t>(st.type()),
            st.id(), managed_ctx.get());

    return managed_ctype<ADM_pfs_storage_t>{c_st, std::move(managed_ctx)};
}

managed_ctype<ADM_dataset_t>
convert(const scord::dataset& dataset) {
    return managed_ctype<ADM_dataset_t>(
            ADM_dataset_create(dataset.id().c_str()));
}

managed_ctype<ADM_dataset_list_t>
convert(const std::vector<scord::dataset>& datasets) {

    std::vector<ADM_dataset_t> tmp;

    std::transform(datasets.cbegin(), datasets.cend(), std::back_inserter(tmp),
                   [](const scord::dataset& d) {
                       return ADM_dataset_create(d.id().c_str());
                   });

    auto rv = managed_ctype<ADM_dataset_list_t>{
            ADM_dataset_list_create(tmp.data(), tmp.size())};

    std::for_each(tmp.cbegin(), tmp.cend(),
                  [](ADM_dataset_t d) { ADM_dataset_destroy(d); });

    return rv;
}

std::vector<scord::dataset>
convert(ADM_dataset_t datasets[], size_t datasets_len) {

    std::vector<scord::dataset> rv;
    rv.reserve(datasets_len);

    for(size_t i = 0; i < datasets_len; ++i) {
        rv.emplace_back(datasets[i]);
    }

    return rv;
}

std::vector<scord::dataset>
convert(ADM_dataset_list_t list) {

    std::vector<scord::dataset> rv;
    rv.reserve(list->l_length);

    for(size_t i = 0; i < list->l_length; ++i) {
        rv.emplace_back(&list->l_datasets[i]);
    }

    return rv;
}

managed_ctype<ADM_job_resources_t>
convert(const job::resources& res) {

    auto managed_nodes = as_ctype_array(res.nodes());

    ADM_job_resources_t c_res = ADM_job_resources_create(managed_nodes.data(),
                                                         managed_nodes.size());

    return managed_ctype<ADM_job_resources_t>{c_res, std::move(managed_nodes)};
}

managed_ctype<ADM_job_requirements_t>
convert(const scord::job_requirements& reqs) {

    const auto& adhoc_storage = reqs.adhoc_storage();

    auto managed_storage = convert(adhoc_storage);
    auto managed_inputs = as_ctype_array(reqs.inputs());
    auto managed_outputs = as_ctype_array(reqs.outputs());

    ADM_job_requirements_t c_reqs = ADM_job_requirements_create(
            managed_inputs.data(), managed_inputs.size(),
            managed_outputs.data(), managed_outputs.size(),
            managed_storage.get());

    return managed_ctype<ADM_job_requirements_t>{
            c_reqs, std::move(managed_inputs), std::move(managed_outputs),
            std::move(managed_storage)};
}


managed_ctype<ADM_job_t>
convert(const job& j) {
    return managed_ctype<ADM_job_t>(ADM_job_create(j.id(), j.slurm_id()));
}

job
convert(ADM_job_t j) {
    return scord::job{j};
}

managed_ctype<ADM_transfer_t>
convert(const transfer& tx) {
    return managed_ctype<ADM_transfer_t>(ADM_transfer_create(tx.id()));
}

transfer
convert(ADM_transfer_t tx) {
    return transfer{tx};
}

managed_ctype<ADM_qos_limit_list_t>
convert(const std::vector<qos::limit>& limits) {

    std::vector<ADM_qos_limit_t> tmp;

    std::transform(
            limits.cbegin(), limits.cend(), std::back_inserter(tmp),
            [](const scord::qos::limit& l) {
                ADM_qos_entity_t e = nullptr;

                if(l.entity()) {

                    switch(const auto s = l.entity()->scope()) {
                        case qos::scope::dataset: {
                            e = ADM_qos_entity_create(
                                    static_cast<ADM_qos_scope_t>(s),
                                    convert(l.entity()->data<scord::dataset>())
                                            .release());
                            break;
                        }

                        case qos::scope::node: {
                            e = ADM_qos_entity_create(
                                    static_cast<ADM_qos_scope_t>(s),
                                    convert(l.entity()->data<scord::node>())
                                            .release());
                            break;
                        }

                        case qos::scope::job: {
                            e = ADM_qos_entity_create(
                                    static_cast<ADM_qos_scope_t>(s),
                                    convert(l.entity()->data<scord::job>())
                                            .release());
                            break;
                        }

                        case qos::scope::transfer: {
                            e = ADM_qos_entity_create(
                                    static_cast<ADM_qos_scope_t>(s),
                                    convert(l.entity()->data<scord::transfer>())
                                            .release());
                            break;
                        }
                    }
                }

                return ADM_qos_limit_create(
                        e, static_cast<ADM_qos_class_t>(l.subclass()),
                        l.value());
            });

    auto rv = managed_ctype<ADM_qos_limit_list_t>{
            ADM_qos_limit_list_create(tmp.data(), tmp.size())};

    std::for_each(tmp.cbegin(), tmp.cend(),
                  [](ADM_qos_limit_t l) { ADM_qos_limit_destroy_all(l); });

    return rv;
}

std::vector<qos::limit>
convert(ADM_qos_limit_t limits[], size_t limits_len) {

    std::vector<scord::qos::limit> rv;
    rv.reserve(limits_len);

    for(size_t i = 0; i < limits_len; ++i) {
        rv.emplace_back(limits[i]);
    }

    return rv;
}

std::vector<scord::qos::limit>
convert(ADM_qos_limit_list_t list) {

    std::vector<scord::qos::limit> rv;
    rv.reserve(list->l_length);

    for(size_t i = 0; i < list->l_length; ++i) {
        rv.emplace_back(&list->l_limits[i]);
    }

    return rv;
}


} // namespace scord::api

src/common/api/convert.hpp

deleted100644 → 0
+0 −500
Original line number Diff line number Diff line
/******************************************************************************
 * Copyright 2021-2022, Barcelona Supercomputing Center (BSC), Spain
 *
 * This software was partially supported by the EuroHPC-funded project ADMIRE
 *   (Project ID: 956748, https://www.admire-eurohpc.eu).
 *
 * This file is part of scord.
 *
 * scord is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * scord is distributed in the hope that 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 scord.  If not, see <https://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *****************************************************************************/

#ifndef SCORD_CONVERT_HPP
#define SCORD_CONVERT_HPP

#include "scord/types.h"
#include "scord/types.hpp"

namespace scord::api {

// convenience types for managing the types from the C API in a RAII fashion
template <typename T>
struct managed_ctype;

template <typename T>
struct managed_ctype_array;

// conversion functions between C API and CXX API types

managed_ctype<ADM_node_t>
convert(const node& node);

managed_ctype<ADM_adhoc_resources_t>
convert(const adhoc_storage::resources& res);

managed_ctype<ADM_adhoc_context_t>
convert(const adhoc_storage::ctx& ctx);

managed_ctype<ADM_adhoc_storage_t>
convert(const scord::adhoc_storage& st);

managed_ctype<ADM_pfs_context_t>
convert(const pfs_storage::ctx& ctx);

managed_ctype<ADM_pfs_storage_t>
convert(const scord::pfs_storage& st);

managed_ctype<ADM_dataset_t>
convert(const scord::dataset& dataset);

managed_ctype<ADM_dataset_list_t>
convert(const std::vector<scord::dataset>& datasets);

std::vector<scord::dataset>
convert(ADM_dataset_t datasets[], size_t datasets_len);

std::vector<scord::dataset>
convert(ADM_dataset_list_t list);

managed_ctype<ADM_job_resources_t>
convert(const job::resources& res);

managed_ctype<ADM_job_requirements_t>
convert(const scord::job_requirements& reqs);

managed_ctype<ADM_job_t>
convert(const job& j);

job
convert(ADM_job_t j);

managed_ctype<ADM_transfer_t>
convert(const transfer& t);

transfer
convert(ADM_transfer_t j);

managed_ctype<ADM_qos_limit_list_t>
convert(const std::vector<qos::limit>& limits);

std::vector<qos::limit>
convert(ADM_qos_limit_t limits[], size_t limits_len);

std::vector<scord::qos::limit>
convert(ADM_qos_limit_list_t list);

} // namespace scord::api


////////////////////////////////////////////////////////////////////////////////
//  Specializations for conversion types
////////////////////////////////////////////////////////////////////////////////

template <>
struct scord::api::managed_ctype<ADM_node_t> {

    explicit managed_ctype(ADM_node_t node) : m_node(node) {}

    ADM_node_t
    get() const {
        return m_node.get();
    }

    ADM_node_t
    release() {
        return m_node.release();
    }

    scord::utils::ctype_ptr<ADM_node_t, ADM_node_destroy> m_node;
};

template <>
struct scord::api::managed_ctype_array<ADM_node_t> {

    managed_ctype_array() = default;

    explicit managed_ctype_array(ADM_node_t* data, size_t size)
        : m_nodes(data, size) {}

    explicit managed_ctype_array(std::vector<ADM_node_t>&& v)
        : m_nodes(v.data(), v.size()) {}

    constexpr size_t
    size() const {
        return m_nodes.size();
    }

    constexpr const ADM_node_t*
    data() const noexcept {
        return m_nodes.data();
    }

    constexpr ADM_node_t*
    data() noexcept {
        return m_nodes.data();
    }

    constexpr ADM_node_t*
    release() noexcept {
        return m_nodes.release();
    }

    scord::utils::ctype_ptr_vector<ADM_node_t, ADM_node_destroy> m_nodes;
};

template <>
struct scord::api::managed_ctype<ADM_adhoc_resources_t> {

    managed_ctype() = default;

    explicit managed_ctype(ADM_adhoc_resources_t res,
                           managed_ctype_array<ADM_node_t>&& nodes)
        : m_adhoc_resources(res), m_nodes(std::move(nodes)) {}

    ADM_adhoc_resources_t
    get() const {
        return m_adhoc_resources.get();
    }

    ADM_adhoc_resources_t
    release() {
        std::ignore = m_nodes.release();
        return m_adhoc_resources.release();
    }

    scord::utils::ctype_ptr<ADM_adhoc_resources_t, ADM_adhoc_resources_destroy>
            m_adhoc_resources;
    managed_ctype_array<ADM_node_t> m_nodes;
};

template <>
struct scord::api::managed_ctype<ADM_adhoc_context_t> {

    managed_ctype() = default;

    explicit managed_ctype(ADM_adhoc_context_t ctx,
                           managed_ctype<ADM_adhoc_resources_t>&& resources)
        : m_adhoc_context(ctx), m_adhoc_resources(std::move(resources)) {}

    ADM_adhoc_context_t
    get() const {
        return m_adhoc_context.get();
    }

    ADM_adhoc_context_t
    release() {
        std::ignore = m_adhoc_resources.release();
        return m_adhoc_context.release();
    }

    scord::utils::ctype_ptr<ADM_adhoc_context_t, ADM_adhoc_context_destroy>
            m_adhoc_context;
    managed_ctype<ADM_adhoc_resources_t> m_adhoc_resources;
};

template <>
struct scord::api::managed_ctype<ADM_adhoc_storage_t> {

    managed_ctype() = default;

    explicit managed_ctype(ADM_adhoc_storage_t st,
                           managed_ctype<ADM_adhoc_context_t>&& ctx)
        : m_storage(st), m_ctx(std::move(ctx)) {}

    ADM_adhoc_storage_t
    get() const {
        return m_storage.get();
    }

    ADM_adhoc_storage_t
    release() {
        std::ignore = m_ctx.release();
        return m_storage.release();
    }

    scord::utils::ctype_ptr<ADM_adhoc_storage_t, ADM_adhoc_storage_destroy>
            m_storage;
    managed_ctype<ADM_adhoc_context_t> m_ctx;
};

template <>
struct scord::api::managed_ctype<ADM_pfs_context_t> {

    managed_ctype() = default;

    explicit managed_ctype(ADM_pfs_context_t ctx) : m_pfs_context(ctx) {}

    ADM_pfs_context_t
    get() const {
        return m_pfs_context.get();
    }

    ADM_pfs_context_t
    release() {
        return m_pfs_context.release();
    }

    scord::utils::ctype_ptr<ADM_pfs_context_t, ADM_pfs_context_destroy>
            m_pfs_context;
};

template <>
struct scord::api::managed_ctype<ADM_pfs_storage_t> {

    managed_ctype() = default;

    explicit managed_ctype(ADM_pfs_storage_t st,
                           managed_ctype<ADM_pfs_context_t>&& ctx)
        : m_storage(st), m_ctx(std::move(ctx)) {}

    ADM_pfs_storage_t
    get() const {
        return m_storage.get();
    }

    ADM_pfs_storage_t
    release() {
        std::ignore = m_ctx.release();
        return m_storage.release();
    }

    scord::utils::ctype_ptr<ADM_pfs_storage_t, ADM_pfs_storage_destroy>
            m_storage;
    managed_ctype<ADM_pfs_context_t> m_ctx;
};

template <>
struct scord::api::managed_ctype<ADM_dataset_t> {

    explicit managed_ctype(ADM_dataset_t dataset) : m_dataset(dataset) {}

    ADM_dataset_t
    get() const {
        return m_dataset.get();
    }

    ADM_dataset_t
    release() {
        return m_dataset.release();
    }

    scord::utils::ctype_ptr<ADM_dataset_t, ADM_dataset_destroy> m_dataset;
};

template <>
struct scord::api::managed_ctype_array<ADM_dataset_t> {

    explicit managed_ctype_array(ADM_dataset_t* data, size_t size)
        : m_datasets(data, size) {}

    explicit managed_ctype_array(std::vector<ADM_dataset_t>&& v)
        : m_datasets(v.data(), v.size()) {}

    constexpr size_t
    size() const {
        return m_datasets.size();
    }

    constexpr const ADM_dataset_t*
    data() const noexcept {
        return m_datasets.data();
    }

    constexpr ADM_dataset_t*
    data() noexcept {
        return m_datasets.data();
    }

    constexpr ADM_dataset_t*
    release() noexcept {
        return m_datasets.release();
    }

    scord::utils::ctype_ptr_vector<ADM_dataset_t, ADM_dataset_destroy>
            m_datasets;
};

template <>
struct scord::api::managed_ctype<ADM_dataset_list_t> {

    explicit managed_ctype(ADM_dataset_list_t list) : m_list(list) {}

    ADM_dataset_list_t
    get() const {
        return m_list.get();
    }

    ADM_dataset_list_t
    release() {
        return m_list.release();
    }

    scord::utils::ctype_ptr<ADM_dataset_list_t, ADM_dataset_list_destroy>
            m_list;
};

template <>
struct scord::api::managed_ctype<ADM_job_resources_t> {

    explicit managed_ctype(ADM_job_resources_t res,
                           managed_ctype_array<ADM_node_t>&& nodes)
        : m_adhoc_resources(res), m_nodes(std::move(nodes)) {}

    ADM_job_resources_t
    get() const {
        return m_adhoc_resources.get();
    }

    ADM_job_resources_t
    release() {
        return m_adhoc_resources.release();
    }

    scord::utils::ctype_ptr<ADM_job_resources_t, ADM_job_resources_destroy>
            m_adhoc_resources;
    managed_ctype_array<ADM_node_t> m_nodes;
};

template <>
struct scord::api::managed_ctype<ADM_job_requirements_t> {

    explicit managed_ctype(ADM_job_requirements_t reqs,
                           managed_ctype_array<ADM_dataset_t>&& inputs,
                           managed_ctype_array<ADM_dataset_t>&& outputs,
                           managed_ctype<ADM_adhoc_storage_t>&& adhoc_storage)
        : m_reqs(reqs), m_inputs(std::move(inputs)),
          m_outputs(std::move(outputs)),
          m_adhoc_storage(std::move(adhoc_storage)) {}

    ADM_job_requirements_t
    get() const {
        return m_reqs.get();
    }

    ADM_job_requirements_t
    release() {
        return m_reqs.release();
    }


    scord::utils::ctype_ptr<ADM_job_requirements_t,
                            ADM_job_requirements_destroy>
            m_reqs;
    managed_ctype_array<ADM_dataset_t> m_inputs;
    managed_ctype_array<ADM_dataset_t> m_outputs;
    managed_ctype<ADM_adhoc_storage_t> m_adhoc_storage;
};


// forward declarations
ADM_return_t
ADM_job_destroy(ADM_job_t job);

ADM_return_t
ADM_transfer_destroy(ADM_transfer_t tx);

template <>
struct scord::api::managed_ctype<ADM_job_t> {

    explicit managed_ctype(ADM_job_t job) : m_job(job) {}

    ADM_job_t
    get() const {
        return m_job.get();
    }

    ADM_job_t
    release() {
        return m_job.release();
    }

    scord::utils::ctype_ptr<ADM_job_t, ADM_job_destroy> m_job;
};

template <>
struct scord::api::managed_ctype<ADM_transfer_t> {

    explicit managed_ctype(ADM_transfer_t tx) : m_transfer(tx) {}

    ADM_transfer_t
    get() const {
        return m_transfer.get();
    }

    ADM_transfer_t
    release() {
        return m_transfer.release();
    }

    scord::utils::ctype_ptr<ADM_transfer_t, ADM_transfer_destroy> m_transfer;
};

ADM_return_t
ADM_qos_limit_destroy_all(ADM_qos_limit_t l);

template <>
struct scord::api::managed_ctype_array<ADM_qos_limit_t> {

    explicit managed_ctype_array(ADM_qos_limit_t data[], size_t size)
        : m_qos_limits(data, size) {}

    explicit managed_ctype_array(std::vector<ADM_qos_limit_t>&& v)
        : m_qos_limits(v.data(), v.size()) {}

    constexpr size_t
    size() const {
        return m_qos_limits.size();
    }

    constexpr const ADM_qos_limit_t*
    data() const noexcept {
        return m_qos_limits.data();
    }

    constexpr ADM_qos_limit_t*
    data() noexcept {
        return m_qos_limits.data();
    }

    constexpr ADM_qos_limit_t*
    release() noexcept {
        return m_qos_limits.release();
    }

    scord::utils::ctype_ptr_vector<ADM_qos_limit_t, ADM_qos_limit_destroy_all>
            m_qos_limits;
};

template <>
struct scord::api::managed_ctype<ADM_qos_limit_list_t> {

    explicit managed_ctype(ADM_qos_limit_list_t list) : m_list(list) {}

    ADM_qos_limit_list_t
    get() const {
        return m_list.get();
    }

    ADM_qos_limit_list_t
    release() {
        return m_list.release();
    }

    scord::utils::ctype_ptr<ADM_qos_limit_list_t, ADM_qos_limit_list_destroy>
            m_list;
};

#endif // SCORD_CONVERT_HPP
+11 −7

File changed.

Preview size limit exceeded, changes collapsed.

+50 −0

File changed.

Preview size limit exceeded, changes collapsed.

Loading