mirror of https://github.com/zeldaret/botw.git
ksys/act: Finish BaseProcHandle
This commit is contained in:
parent
e36bc464b6
commit
4f2c0a6079
|
|
@ -91736,24 +91736,24 @@
|
|||
0x00000071011bb828,sub_71011BB828,92,_ZNK4sead10Delegate1RIN4ksys3act8BaseProcEPvbE5cloneEPNS_4HeapE
|
||||
0x00000071011bb884,sub_71011BB884,32,_GLOBAL__sub_I_actBaseProcHandle.cpp
|
||||
0x00000071011bb8a4,BaseProcHandle::ctor,12,_ZN4ksys3act14BaseProcHandleC1Ev
|
||||
0x00000071011bb8b0,BaseProcHandle::actorReady,32,_ZN4ksys3act14BaseProcHandle9procReadyEv
|
||||
0x00000071011bb8d0,BaseProcHandle::hasActorAndFlags5,84,
|
||||
0x00000071011bb924,BaseProcHandle::x,52,
|
||||
0x00000071011bb958,BaseProcHandle::deleteActor,112,
|
||||
0x00000071011bb9c8,BaseProcHandle::dtor,56,_ZN4ksys3act14BaseProcHandleD1Ev
|
||||
0x00000071011bb8b0,BaseProcHandle::actorReady,32,_ZNK4ksys3act14BaseProcHandle11isProcReadyEv
|
||||
0x00000071011bb8d0,BaseProcHandle::hasActorAndFlags5,84,_ZNK4ksys3act14BaseProcHandle21hasProcCreationFailedEv
|
||||
0x00000071011bb924,BaseProcHandle::x,52,_ZNK4ksys3act14BaseProcHandle23isProcCreationCancelledEv
|
||||
0x00000071011bb958,BaseProcHandle::deleteActor,112,_ZN4ksys3act14BaseProcHandle18deleteProcIfFailedEv
|
||||
0x00000071011bb9c8,BaseProcHandle::dtor,56,_ZN4ksys3act14BaseProcHandle10deleteProcEv
|
||||
0x00000071011bba00,BaseProcUnit::deleteActor,316,_ZN4ksys3act12BaseProcUnit10deleteProcEjPNS0_14BaseProcHandleE
|
||||
0x00000071011bbb3c,BaseProcHandle::getActor,24,_ZN4ksys3act14BaseProcHandle7getProcEv
|
||||
0x00000071011bbb54,BaseProcHandle::setProcStateFlag8000,184,
|
||||
0x00000071011bbc0c,BaseProcHandle::wakeUpActorAndReleaseUnit,184,
|
||||
0x00000071011bbcc4,BaseProcHandle::getBaseProcEvent,48,
|
||||
0x00000071011bbcf4,BaseProcHandle::allocUnit,228,
|
||||
0x00000071011bbb3c,BaseProcHandle::getActor,24,_ZNK4ksys3act14BaseProcHandle7getProcEv
|
||||
0x00000071011bbb54,BaseProcHandle::setProcStateFlag8000,184,_ZN4ksys3act14BaseProcHandle11releaseProcEv
|
||||
0x00000071011bbc0c,BaseProcHandle::wakeUpActorAndReleaseUnit,184,_ZN4ksys3act14BaseProcHandle18releaseAndWakeProcEv
|
||||
0x00000071011bbcc4,BaseProcHandle::getBaseProcEvent,48,_ZNK4ksys3act14BaseProcHandle13getCreateTaskEv
|
||||
0x00000071011bbcf4,BaseProcHandle::allocUnit,228,_ZN4ksys3act14BaseProcHandle9allocUnitEv
|
||||
0x00000071011bbdd8,BaseProcUnit::setActor,540,_ZN4ksys3act12BaseProcUnit7setProcEPNS0_8BaseProcE
|
||||
0x00000071011bbff4,BaseProcUnit::cleanUp,512,_ZN4ksys3act12BaseProcUnit7cleanUpEPNS0_8BaseProcEb
|
||||
0x00000071011bc1f4,BaseProcUnit::unlinkActor,412,_ZN4ksys3act12BaseProcUnit10unlinkProcEPNS0_8BaseProcE
|
||||
0x00000071011bc390,BaseProcUnit::isParentHandleDefault,24,_ZNK4ksys3act12BaseProcUnit21isParentHandleDefaultEv
|
||||
0x00000071011bc3a8,sub_71011BC3A8,132,
|
||||
0x00000071011bc42c,sub_71011BC42C,56,
|
||||
0x00000071011bc464,sinitBaseProcHandle,172,
|
||||
0x00000071011bc3a8,sub_71011BC3A8,132,_ZN4ksys3act16BaseProcUnitPoolD2Ev
|
||||
0x00000071011bc42c,sub_71011BC42C,56,_ZN4ksys3act14BaseProcHandleD1Ev
|
||||
0x00000071011bc464,sinitBaseProcHandle,172,_GLOBAL__sub_I_actBaseProcUnit.cpp?
|
||||
0x00000071011bc510,_ZN12BaseProcLinkC2Ev,20,_ZN4ksys3act12BaseProcLinkC1Ev
|
||||
0x00000071011bc524,_ZN12BaseProcLink12acquireActorER13ActorAccessorP9ActorBase,308,_ZNK4ksys3act12BaseProcLink7getProcEPNS0_24ActorLinkConstDataAccessEPNS0_8BaseProcE
|
||||
0x00000071011bc658,BaseProcLinkData::lockCritSectionOnGameThreadOrHavokThread,68,_ZN4ksys3act16BaseProcLinkData12lockIfNeededEv
|
||||
|
|
|
|||
|
Can't render this file because it is too large.
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
protected:
|
||||
friend class ActorConstDataAccess;
|
||||
friend class BaseProc;
|
||||
friend class BaseProcHandle;
|
||||
friend class BaseProcUnit;
|
||||
|
||||
bool mAcquired = false;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#include <container/seadListImpl.h>
|
||||
#include <container/seadSafeArray.h>
|
||||
#include <container/seadTreeMap.h>
|
||||
#include <math/seadMathCalcCommon.h>
|
||||
#include <prim/seadBitFlag.h>
|
||||
#include <prim/seadRuntimeTypeInfo.h>
|
||||
#include <prim/seadSafeString.h>
|
||||
|
|
@ -122,7 +123,6 @@ public:
|
|||
|
||||
void setCreatePriorityState1();
|
||||
void setCreatePriorityState2();
|
||||
bool setStateFlag(u32 flag_bit);
|
||||
|
||||
void onJobPush(JobType type) {
|
||||
onJobPush1_(type);
|
||||
|
|
@ -135,6 +135,7 @@ public:
|
|||
/// Set the BaseProcUnit. Only for use by BaseProcCreateTask.
|
||||
void setUnitForBaseProcCreateTask(BaseProcUnit* unit) { mProcUnit = unit; }
|
||||
void setInitializedFlag() { mFlags.set(Flags::Initialized); }
|
||||
bool requestDeleteProcUnit() { return setStateFlag(StateFlags::RequestDeleteProcUnit); }
|
||||
|
||||
protected:
|
||||
friend class BaseProcLinkDataMgr;
|
||||
|
|
@ -279,6 +280,9 @@ protected:
|
|||
BaseProcJobHandler*& getJobHandler(JobType type) { return mJobHandlers[int(type)]; }
|
||||
BaseProcJobHandler* getJobHandler(JobType type) const { return mJobHandlers[int(type)]; }
|
||||
|
||||
bool setStateFlag(u32 flag_bit);
|
||||
bool setStateFlag(StateFlags flag) { return setStateFlag(sead::log2(u32(flag))); }
|
||||
|
||||
bool x00000071011ba9fc();
|
||||
|
||||
sead::FixedSafeString<64> mName;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
#include "KingSystem/ActorSystem/actBaseProcHandle.h"
|
||||
#include <prim/seadScopedLock.h>
|
||||
#include "KingSystem/ActorSystem/actActorLinkConstDataAccess.h"
|
||||
#include "KingSystem/ActorSystem/actBaseProc.h"
|
||||
#include "KingSystem/ActorSystem/actBaseProcUnit.h"
|
||||
#include "KingSystem/Utils/InitTimeInfo.h"
|
||||
|
||||
|
|
@ -6,28 +9,159 @@ namespace ksys::act {
|
|||
|
||||
static util::InitTimeInfo sInfo;
|
||||
|
||||
BaseProcHandle::BaseProcHandle() {
|
||||
mUnit = nullptr;
|
||||
mFlag = 0;
|
||||
}
|
||||
BaseProcHandle::BaseProcHandle() = default;
|
||||
|
||||
BaseProcHandle::~BaseProcHandle() {
|
||||
deleteProc();
|
||||
}
|
||||
|
||||
bool BaseProcHandle::isProcReady() const {
|
||||
return mUnit && mUnit->isReady();
|
||||
}
|
||||
|
||||
bool BaseProcHandle::hasProcCreationFailed() const {
|
||||
if (mFailed)
|
||||
return true;
|
||||
|
||||
if (!mUnit)
|
||||
return false;
|
||||
|
||||
const auto status = mUnit->getStatus();
|
||||
if (status == BaseProcUnit::Status::NoProc || status == BaseProcUnit::Status::Cancelled)
|
||||
return true;
|
||||
|
||||
if (mUnit->getCreateTask().getStatus() == util::Task::Status::RemovedFromQueue ||
|
||||
status == BaseProcUnit::Status::Cancelled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseProcHandle::isProcCreationCancelled() const {
|
||||
if (!mUnit)
|
||||
return false;
|
||||
|
||||
if (mUnit->getCreateTask().getStatus() == util::Task::Status::RemovedFromQueue)
|
||||
return true;
|
||||
|
||||
if (mUnit->getStatus() == BaseProcUnit::Status::Cancelled)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BaseProcHandle::deleteProcIfFailed() {
|
||||
if (mFailed && !mUnit) {
|
||||
mFailed = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (hasProcCreationFailed())
|
||||
deleteProc();
|
||||
}
|
||||
|
||||
void BaseProcHandle::deleteProc() {
|
||||
if (mUnit) {
|
||||
mUnit->deleteProc(0, this);
|
||||
mUnit = nullptr;
|
||||
}
|
||||
mFlag = 0;
|
||||
mFailed = false;
|
||||
}
|
||||
|
||||
bool BaseProcHandle::procReady() {
|
||||
return mUnit && mUnit->isReady();
|
||||
}
|
||||
|
||||
BaseProc* BaseProcHandle::getProc() {
|
||||
BaseProc* BaseProcHandle::getProc() const {
|
||||
if (mUnit)
|
||||
return mUnit->getProc();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
BaseProc* BaseProcHandle::releaseProc() {
|
||||
if (!isProcReady())
|
||||
return nullptr;
|
||||
|
||||
BaseProcUnit* unit = mUnit;
|
||||
BaseProc* proc = nullptr;
|
||||
|
||||
{
|
||||
ActorLinkConstDataAccess accessor;
|
||||
{
|
||||
const auto lock = sead::makeScopedLock(unit->getCS());
|
||||
if (unit->compareExchangeHandle(this, &gDummyProcHandle)) {
|
||||
if (unit->getProc())
|
||||
accessor.acquire(unit->getProc());
|
||||
}
|
||||
}
|
||||
|
||||
proc = accessor.mProc;
|
||||
if (proc)
|
||||
proc->requestDeleteProcUnit();
|
||||
}
|
||||
|
||||
mUnit = nullptr;
|
||||
return proc;
|
||||
}
|
||||
|
||||
BaseProc* BaseProcHandle::releaseAndWakeProc() {
|
||||
if (!isProcReady())
|
||||
return nullptr;
|
||||
|
||||
BaseProcUnit* unit = mUnit;
|
||||
BaseProc* proc = nullptr;
|
||||
|
||||
{
|
||||
ActorLinkConstDataAccess accessor;
|
||||
{
|
||||
const auto lock = sead::makeScopedLock(unit->getCS());
|
||||
if (unit->compareExchangeHandle(this, &gDummyProcHandle)) {
|
||||
if (unit->getProc())
|
||||
accessor.acquire(unit->getProc());
|
||||
}
|
||||
}
|
||||
|
||||
proc = accessor.mProc;
|
||||
if (proc)
|
||||
proc->wakeUp(BaseProc::SleepWakeReason::_0);
|
||||
}
|
||||
|
||||
mUnit = nullptr;
|
||||
return proc;
|
||||
}
|
||||
|
||||
BaseProcCreateTask* BaseProcHandle::getCreateTask() const {
|
||||
sead::Atomic<BaseProcUnit*> unit = mUnit;
|
||||
auto* ret = unit ? &unit->getCreateTask() : nullptr;
|
||||
static_cast<void>(unit.load());
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool BaseProcHandle::allocUnit() {
|
||||
if (mUnit)
|
||||
return false;
|
||||
|
||||
int idx = gUnitPool.idx;
|
||||
for (int i = 0; i < gUnitPool.units.size(); ++idx, ++i) {
|
||||
idx = (idx == gUnitPool.units.size()) ? 0 : idx;
|
||||
auto* unit = gUnitPool.get(idx);
|
||||
|
||||
if (!unit->getCreateTask().canSubmitRequest())
|
||||
continue;
|
||||
if (unit->getProc() != nullptr)
|
||||
continue;
|
||||
if (!unit->compareExchangeHandle(nullptr, this))
|
||||
continue;
|
||||
|
||||
gUnitPool.idx = idx + 1;
|
||||
mUnit = unit;
|
||||
if (!mUnit)
|
||||
return false;
|
||||
|
||||
mUnit->setInitializingStatus();
|
||||
return true;
|
||||
}
|
||||
|
||||
mUnit = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace ksys::act
|
||||
|
|
|
|||
|
|
@ -14,21 +14,26 @@ public:
|
|||
BaseProcHandle();
|
||||
~BaseProcHandle();
|
||||
|
||||
bool procReady();
|
||||
bool isProcReady() const;
|
||||
bool hasProcCreationFailed() const;
|
||||
bool isProcCreationCancelled() const;
|
||||
|
||||
BaseProc* getProc();
|
||||
void deleteProcIfFailed();
|
||||
void deleteProc();
|
||||
|
||||
BaseProc* getProc() const;
|
||||
BaseProc* releaseProc();
|
||||
BaseProc* releaseAndWakeProc();
|
||||
BaseProcCreateTask* getCreateTask() const;
|
||||
BaseProcUnit* getUnit() const { return mUnit; }
|
||||
bool allocUnit();
|
||||
BaseProcCreateTask* getCreateTask() const;
|
||||
|
||||
bool getFlag() const { return mFlag; }
|
||||
void setFlag(bool flag) { mFlag = flag; }
|
||||
|
||||
static BaseProcHandle sDummyHandle;
|
||||
bool hasFailed() const { return mFailed; }
|
||||
void setFailed(bool failed) { mFailed = failed; }
|
||||
|
||||
private:
|
||||
BaseProcUnit* mUnit;
|
||||
bool mFlag;
|
||||
BaseProcUnit* mUnit = nullptr;
|
||||
bool mFailed = false;
|
||||
};
|
||||
KSYS_CHECK_SIZE_NX150(BaseProcHandle, 0x10);
|
||||
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ void BaseProcInitializer::deleteThreadIfPaused() {
|
|||
bool BaseProcInitializer::requestCreateBaseProc(const BaseProcCreateRequest& req) {
|
||||
if (!mActorGenerationEnabled) {
|
||||
if (req.task_data->mProcHandle)
|
||||
req.task_data->mProcHandle->setFlag(true);
|
||||
req.task_data->mProcHandle->setFailed(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -112,13 +112,13 @@ bool BaseProcInitializer::requestCreateBaseProc(const BaseProcCreateRequest& req
|
|||
util::TaskMgrRequest mgr_req;
|
||||
if (req.task_data->mProcHandle) {
|
||||
if (!req.task_data->mProcHandle->allocUnit()) {
|
||||
req.task_data->mProcHandle->setFlag(true);
|
||||
req.task_data->mProcHandle->setFailed(true);
|
||||
return false;
|
||||
}
|
||||
|
||||
mgr_req.task = req.task_data->mProcHandle->getCreateTask();
|
||||
if (!mgr_req.task) {
|
||||
req.task_data->mProcHandle->setFlag(true);
|
||||
req.task_data->mProcHandle->setFailed(true);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,19 @@
|
|||
|
||||
namespace ksys::act {
|
||||
|
||||
bool BaseProcUnit::deleteProc(u32, BaseProcHandle* handle) {
|
||||
BaseProcUnitPool gUnitPool{};
|
||||
BaseProcHandle gDummyProcHandle;
|
||||
|
||||
BaseProcUnit::~BaseProcUnit() {
|
||||
deleteProc(0, nullptr);
|
||||
}
|
||||
|
||||
bool BaseProcUnit::deleteProc([[maybe_unused]] u32 x, BaseProcHandle* handle) {
|
||||
#ifdef MATCHING_HACK_NX_CLANG
|
||||
// Ensure x is not optimized out.
|
||||
__builtin_assume(x);
|
||||
#endif
|
||||
|
||||
ActorLinkConstDataAccess accessor;
|
||||
|
||||
{
|
||||
|
|
@ -18,8 +30,8 @@ bool BaseProcUnit::deleteProc(u32, BaseProcHandle* handle) {
|
|||
if (mProc)
|
||||
accessor.acquire(mProc);
|
||||
|
||||
if (mStatus == Status::_1 || mProc) {
|
||||
mHandle.compareExchange(handle, &BaseProcHandle::sDummyHandle);
|
||||
if (mStatus == Status::Initializing || mProc) {
|
||||
mHandle.compareExchange(handle, &gDummyProcHandle);
|
||||
} else {
|
||||
mStatus = Status::Unused;
|
||||
mHandle = nullptr;
|
||||
|
|
@ -65,7 +77,7 @@ bool BaseProcUnit::setProc(BaseProc* proc) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (mStatus != Status::_1)
|
||||
if (mStatus != Status::Initializing)
|
||||
print_info();
|
||||
|
||||
mProc = proc;
|
||||
|
|
@ -75,7 +87,7 @@ bool BaseProcUnit::setProc(BaseProc* proc) {
|
|||
|
||||
void BaseProcUnit::reset() {
|
||||
auto* handle = getHandle();
|
||||
if (handle != &BaseProcHandle::sDummyHandle) {
|
||||
if (handle != &gDummyProcHandle) {
|
||||
sead::FixedSafeString<256> message;
|
||||
message.format("BaseProcUnit:%p, %p", this, handle);
|
||||
util::PrintDebug(message);
|
||||
|
|
@ -85,7 +97,7 @@ void BaseProcUnit::reset() {
|
|||
mHandle = nullptr;
|
||||
}
|
||||
|
||||
void BaseProcUnit::cleanUp(BaseProc* proc, bool set_status_5) {
|
||||
void BaseProcUnit::cleanUp(BaseProc* proc, bool is_cancellation) {
|
||||
const auto lock = sead::makeScopedLock(mCS);
|
||||
|
||||
mProc = nullptr;
|
||||
|
|
@ -102,9 +114,9 @@ void BaseProcUnit::cleanUp(BaseProc* proc, bool set_status_5) {
|
|||
print_info();
|
||||
} else {
|
||||
const auto status = mStatus.load();
|
||||
if (status == Status::Unused || (status > Status::_3 && status != Status::_5))
|
||||
if (status == Status::Unused || (status > Status::NoProc && status != Status::Cancelled))
|
||||
print_info();
|
||||
mStatus = set_status_5 ? Status::_5 : Status::_3;
|
||||
mStatus = is_cancellation ? Status::Cancelled : Status::NoProc;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +135,7 @@ void BaseProcUnit::unlinkProc(BaseProc* proc) {
|
|||
}
|
||||
|
||||
bool BaseProcUnit::isParentHandleDefault() const {
|
||||
return mHandle == &BaseProcHandle::sDummyHandle;
|
||||
return mHandle == &gDummyProcHandle;
|
||||
}
|
||||
|
||||
} // namespace ksys::act
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <basis/seadTypes.h>
|
||||
#include <container/seadSafeArray.h>
|
||||
#include <thread/seadAtomic.h>
|
||||
#include <thread/seadCriticalSection.h>
|
||||
#include "KingSystem/ActorSystem/actBaseProcCreateTask.h"
|
||||
|
|
@ -12,25 +13,44 @@ class BaseProcHandle;
|
|||
|
||||
class BaseProcUnit {
|
||||
public:
|
||||
enum class Status : u32 {
|
||||
Unused = 0,
|
||||
Initializing = 1,
|
||||
Ready = 2,
|
||||
NoProc = 3,
|
||||
_4 = 4,
|
||||
Cancelled = 5,
|
||||
};
|
||||
|
||||
BaseProcUnit() = default;
|
||||
~BaseProcUnit();
|
||||
|
||||
BaseProcUnit(const BaseProcUnit&) = delete;
|
||||
BaseProcUnit(BaseProcUnit&&) = delete;
|
||||
auto operator=(const BaseProcUnit&) = delete;
|
||||
auto operator=(BaseProcUnit&&) = delete;
|
||||
|
||||
bool deleteProc(u32, BaseProcHandle* handle);
|
||||
bool setProc(BaseProc* proc);
|
||||
void unlinkProc(BaseProc* proc);
|
||||
void cleanUp(BaseProc* proc, bool set_status_5);
|
||||
void cleanUp(BaseProc* proc, bool is_cancellation);
|
||||
bool isParentHandleDefault() const;
|
||||
|
||||
Status getStatus() const { return mStatus; }
|
||||
BaseProc* getProc() const { return mProc; }
|
||||
BaseProcCreateTask& getCreateTask() { return mCreateTask; }
|
||||
const BaseProcCreateTask& getCreateTask() const { return mCreateTask; }
|
||||
sead::CriticalSection& getCS() { return mCS; }
|
||||
|
||||
bool isReady() const { return mStatus == Status::Ready; }
|
||||
|
||||
private:
|
||||
enum class Status : u32 {
|
||||
Unused = 0,
|
||||
_1 = 1,
|
||||
Ready = 2,
|
||||
_3 = 3,
|
||||
_4 = 4,
|
||||
_5 = 5,
|
||||
};
|
||||
bool compareExchangeHandle(BaseProcHandle* expected, BaseProcHandle* desired) {
|
||||
return mHandle.compareExchange(expected, desired);
|
||||
}
|
||||
|
||||
void setInitializingStatus() { mStatus = Status::Initializing; }
|
||||
|
||||
private:
|
||||
void reset();
|
||||
|
||||
BaseProcHandle* getHandle() const { return mHandle.load(); }
|
||||
|
|
@ -38,9 +58,19 @@ private:
|
|||
sead::Atomic<Status> mStatus = Status::Unused;
|
||||
sead::Atomic<BaseProcHandle*> mHandle{};
|
||||
BaseProc* mProc{};
|
||||
BaseProcCreateTask mCreateTask;
|
||||
BaseProcCreateTask mCreateTask{nullptr};
|
||||
sead::CriticalSection mCS;
|
||||
};
|
||||
KSYS_CHECK_SIZE_NX150(BaseProcUnit, 0x2f0);
|
||||
|
||||
struct BaseProcUnitPool {
|
||||
BaseProcUnit* get(int i) { return &units[i]; }
|
||||
|
||||
sead::SafeArray<BaseProcUnit, 256> units;
|
||||
int idx = 0;
|
||||
};
|
||||
|
||||
extern BaseProcUnitPool gUnitPool;
|
||||
extern BaseProcHandle gDummyProcHandle;
|
||||
|
||||
} // namespace ksys::act
|
||||
|
|
|
|||
Loading…
Reference in New Issue