- created a Kernel namespace
- cleaned up Kernel code a bit (moved stuff into namespace, fixed whitespace issues) - added handle types for all different CTROS handles
This commit is contained in:
parent
0886dc70ed
commit
44336329ed
@ -12,22 +12,16 @@
|
|||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/thread.h"
|
#include "core/hle/kernel/thread.h"
|
||||||
|
|
||||||
KernelObjectPool g_kernel_objects;
|
namespace Kernel {
|
||||||
|
|
||||||
void __KernelInit() {
|
ObjectPool g_object_pool;
|
||||||
__KernelThreadingInit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void __KernelShutdown() {
|
ObjectPool::ObjectPool() {
|
||||||
__KernelThreadingShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
KernelObjectPool::KernelObjectPool() {
|
|
||||||
memset(occupied, 0, sizeof(bool) * MAX_COUNT);
|
memset(occupied, 0, sizeof(bool) * MAX_COUNT);
|
||||||
next_id = INITIAL_NEXT_ID;
|
next_id = INITIAL_NEXT_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
Handle KernelObjectPool::Create(KernelObject *obj, int range_bottom, int range_top) {
|
Handle ObjectPool::Create(Object* obj, int range_bottom, int range_top) {
|
||||||
if (range_top > MAX_COUNT) {
|
if (range_top > MAX_COUNT) {
|
||||||
range_top = MAX_COUNT;
|
range_top = MAX_COUNT;
|
||||||
}
|
}
|
||||||
@ -46,8 +40,7 @@ Handle KernelObjectPool::Create(KernelObject *obj, int range_bottom, int range_t
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KernelObjectPool::IsValid(Handle handle)
|
bool ObjectPool::IsValid(Handle handle) {
|
||||||
{
|
|
||||||
int index = handle - HANDLE_OFFSET;
|
int index = handle - HANDLE_OFFSET;
|
||||||
if (index < 0)
|
if (index < 0)
|
||||||
return false;
|
return false;
|
||||||
@ -57,26 +50,24 @@ bool KernelObjectPool::IsValid(Handle handle)
|
|||||||
return occupied[index];
|
return occupied[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelObjectPool::Clear()
|
void ObjectPool::Clear() {
|
||||||
{
|
for (int i = 0; i < MAX_COUNT; i++) {
|
||||||
for (int i = 0; i < MAX_COUNT; i++)
|
|
||||||
{
|
|
||||||
//brutally clear everything, no validation
|
//brutally clear everything, no validation
|
||||||
if (occupied[i])
|
if (occupied[i])
|
||||||
delete pool[i];
|
delete pool[i];
|
||||||
occupied[i] = false;
|
occupied[i] = false;
|
||||||
}
|
}
|
||||||
memset(pool, 0, sizeof(KernelObject*)*MAX_COUNT);
|
memset(pool, 0, sizeof(Object*)*MAX_COUNT);
|
||||||
next_id = INITIAL_NEXT_ID;
|
next_id = INITIAL_NEXT_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
KernelObject *&KernelObjectPool::operator [](Handle handle)
|
Object* &ObjectPool::operator [](Handle handle)
|
||||||
{
|
{
|
||||||
_dbg_assert_msg_(KERNEL, IsValid(handle), "GRABBING UNALLOCED KERNEL OBJ");
|
_dbg_assert_msg_(KERNEL, IsValid(handle), "GRABBING UNALLOCED KERNEL OBJ");
|
||||||
return pool[handle - HANDLE_OFFSET];
|
return pool[handle - HANDLE_OFFSET];
|
||||||
}
|
}
|
||||||
|
|
||||||
void KernelObjectPool::List() {
|
void ObjectPool::List() {
|
||||||
for (int i = 0; i < MAX_COUNT; i++) {
|
for (int i = 0; i < MAX_COUNT; i++) {
|
||||||
if (occupied[i]) {
|
if (occupied[i]) {
|
||||||
if (pool[i]) {
|
if (pool[i]) {
|
||||||
@ -87,18 +78,16 @@ void KernelObjectPool::List() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int KernelObjectPool::GetCount()
|
int ObjectPool::GetCount() {
|
||||||
{
|
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (int i = 0; i < MAX_COUNT; i++)
|
for (int i = 0; i < MAX_COUNT; i++) {
|
||||||
{
|
|
||||||
if (occupied[i])
|
if (occupied[i])
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
KernelObject *KernelObjectPool::CreateByIDType(int type) {
|
Object* ObjectPool::CreateByIDType(int type) {
|
||||||
// Used for save states. This is ugly, but what other way is there?
|
// Used for save states. This is ugly, but what other way is there?
|
||||||
switch (type) {
|
switch (type) {
|
||||||
//case SCE_KERNEL_TMID_Alarm:
|
//case SCE_KERNEL_TMID_Alarm:
|
||||||
@ -142,8 +131,18 @@ KernelObject *KernelObjectPool::CreateByIDType(int type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Init() {
|
||||||
|
__KernelThreadingInit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shutdown() {
|
||||||
|
__KernelThreadingShutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool __KernelLoadExec(u32 entry_point) {
|
bool __KernelLoadExec(u32 entry_point) {
|
||||||
__KernelInit();
|
Kernel::Init();
|
||||||
|
|
||||||
Core::g_app_core->SetPC(entry_point);
|
Core::g_app_core->SetPC(entry_point);
|
||||||
|
|
||||||
|
@ -9,41 +9,50 @@
|
|||||||
typedef u32 Handle;
|
typedef u32 Handle;
|
||||||
typedef s32 Result;
|
typedef s32 Result;
|
||||||
|
|
||||||
enum KernelIDType {
|
namespace Kernel {
|
||||||
KERNEL_ID_TYPE_THREAD,
|
|
||||||
KERNEL_ID_TYPE_SEMAPHORE,
|
enum class HandleType : u32 {
|
||||||
KERNEL_ID_TYPE_MUTEX,
|
Unknown = 0,
|
||||||
KERNEL_ID_TYPE_EVENT,
|
Port = 1,
|
||||||
KERNEL_ID_TYPE_SERVICE,
|
Service = 2,
|
||||||
|
Event = 3,
|
||||||
|
Mutex = 4,
|
||||||
|
SharedMemory = 5,
|
||||||
|
Redirection = 6,
|
||||||
|
Thread = 7,
|
||||||
|
Process = 8,
|
||||||
|
Arbiter = 9,
|
||||||
|
File = 10,
|
||||||
|
Semaphore = 11,
|
||||||
};
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
KERNEL_MAX_NAME_LENGTH = 0x100,
|
MAX_NAME_LENGTH = 0x100,
|
||||||
KERNEL_DEFAULT_STACK_SIZE = 0x4000,
|
DEFAULT_STACK_SIZE = 0x4000,
|
||||||
};
|
};
|
||||||
|
|
||||||
class KernelObjectPool;
|
class ObjectPool;
|
||||||
|
|
||||||
class KernelObject {
|
class Object : NonCopyable {
|
||||||
friend class KernelObjectPool;
|
friend class ObjectPool;
|
||||||
u32 handle;
|
u32 handle;
|
||||||
public:
|
public:
|
||||||
virtual ~KernelObject() {}
|
virtual ~Object() {}
|
||||||
Handle GetHandle() const { return handle; }
|
Handle GetHandle() const { return handle; }
|
||||||
virtual const char *GetTypeName() { return "[BAD KERNEL OBJECT TYPE]"; }
|
virtual const char *GetTypeName() { return "[BAD KERNEL OBJECT TYPE]"; }
|
||||||
virtual const char *GetName() { return "[UNKNOWN KERNEL OBJECT]"; }
|
virtual const char *GetName() { return "[UNKNOWN KERNEL OBJECT]"; }
|
||||||
virtual KernelIDType GetIDType() const = 0;
|
virtual Kernel::HandleType GetHandleType() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class KernelObjectPool {
|
class ObjectPool : NonCopyable {
|
||||||
public:
|
public:
|
||||||
KernelObjectPool();
|
ObjectPool();
|
||||||
~KernelObjectPool() {}
|
~ObjectPool() {}
|
||||||
|
|
||||||
// Allocates a handle within the range and inserts the object into the map.
|
// Allocates a handle within the range and inserts the object into the map.
|
||||||
Handle Create(KernelObject *obj, int range_bottom=INITIAL_NEXT_ID, int range_top=0x7FFFFFFF);
|
Handle Create(Object* obj, int range_bottom=INITIAL_NEXT_ID, int range_top=0x7FFFFFFF);
|
||||||
|
|
||||||
static KernelObject *CreateByIDType(int type);
|
static Object* CreateByIDType(int type);
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
u32 Destroy(Handle handle) {
|
u32 Destroy(Handle handle) {
|
||||||
@ -71,7 +80,7 @@ public:
|
|||||||
// it just acted as a static case and everything worked. This means that we will never
|
// it just acted as a static case and everything worked. This means that we will never
|
||||||
// see the Wrong type object error below, but we'll just have to live with that danger.
|
// see the Wrong type object error below, but we'll just have to live with that danger.
|
||||||
T* t = static_cast<T*>(pool[handle - HANDLE_OFFSET]);
|
T* t = static_cast<T*>(pool[handle - HANDLE_OFFSET]);
|
||||||
if (t == 0 || t->GetIDType() != T::GetStaticIDType()) {
|
if (t == 0 || t->GetHandleType() != T::GetStaticHandleType()) {
|
||||||
WARN_LOG(KERNEL, "Kernel: Wrong object type for %i (%08x)", handle, handle);
|
WARN_LOG(KERNEL, "Kernel: Wrong object type for %i (%08x)", handle, handle);
|
||||||
outError = 0;//T::GetMissingErrorCode();
|
outError = 0;//T::GetMissingErrorCode();
|
||||||
return 0;
|
return 0;
|
||||||
@ -86,17 +95,17 @@ public:
|
|||||||
T *GetFast(Handle handle) {
|
T *GetFast(Handle handle) {
|
||||||
const Handle realHandle = handle - HANDLE_OFFSET;
|
const Handle realHandle = handle - HANDLE_OFFSET;
|
||||||
_dbg_assert_(KERNEL, realHandle >= 0 && realHandle < MAX_COUNT && occupied[realHandle]);
|
_dbg_assert_(KERNEL, realHandle >= 0 && realHandle < MAX_COUNT && occupied[realHandle]);
|
||||||
return static_cast<T *>(pool[realHandle]);
|
return static_cast<T*>(pool[realHandle]);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T, typename ArgT>
|
template <class T, typename ArgT>
|
||||||
void Iterate(bool func(T *, ArgT), ArgT arg) {
|
void Iterate(bool func(T*, ArgT), ArgT arg) {
|
||||||
int type = T::GetStaticIDType();
|
int type = T::GetStaticIDType();
|
||||||
for (int i = 0; i < MAX_COUNT; i++)
|
for (int i = 0; i < MAX_COUNT; i++)
|
||||||
{
|
{
|
||||||
if (!occupied[i])
|
if (!occupied[i])
|
||||||
continue;
|
continue;
|
||||||
T *t = static_cast<T *>(pool[i]);
|
T* t = static_cast<T*>(pool[i]);
|
||||||
if (t->GetIDType() == type) {
|
if (t->GetIDType() == type) {
|
||||||
if (!func(t, arg))
|
if (!func(t, arg))
|
||||||
break;
|
break;
|
||||||
@ -104,33 +113,37 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetIDType(Handle handle, int *type) const {
|
bool GetIDType(Handle handle, HandleType* type) const {
|
||||||
if ((handle < HANDLE_OFFSET) || (handle >= HANDLE_OFFSET + MAX_COUNT) ||
|
if ((handle < HANDLE_OFFSET) || (handle >= HANDLE_OFFSET + MAX_COUNT) ||
|
||||||
!occupied[handle - HANDLE_OFFSET]) {
|
!occupied[handle - HANDLE_OFFSET]) {
|
||||||
ERROR_LOG(KERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle);
|
ERROR_LOG(KERNEL, "Kernel: Bad object handle %i (%08x)", handle, handle);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
KernelObject *t = pool[handle - HANDLE_OFFSET];
|
Object* t = pool[handle - HANDLE_OFFSET];
|
||||||
*type = t->GetIDType();
|
*type = t->GetHandleType();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
KernelObject *&operator [](Handle handle);
|
Object* &operator [](Handle handle);
|
||||||
void List();
|
void List();
|
||||||
void Clear();
|
void Clear();
|
||||||
int GetCount();
|
int GetCount();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
MAX_COUNT = 0x1000,
|
MAX_COUNT = 0x1000,
|
||||||
HANDLE_OFFSET = 0x100,
|
HANDLE_OFFSET = 0x100,
|
||||||
INITIAL_NEXT_ID = 0x10,
|
INITIAL_NEXT_ID = 0x10,
|
||||||
};
|
};
|
||||||
KernelObject *pool[MAX_COUNT];
|
|
||||||
bool occupied[MAX_COUNT];
|
Object* pool[MAX_COUNT];
|
||||||
int next_id;
|
bool occupied[MAX_COUNT];
|
||||||
|
int next_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern KernelObjectPool g_kernel_objects;
|
extern ObjectPool g_object_pool;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool __KernelLoadExec(u32 entry_point);
|
bool __KernelLoadExec(u32 entry_point);
|
||||||
|
@ -42,14 +42,14 @@ enum WaitType {
|
|||||||
WAITTYPE_SYNCH,
|
WAITTYPE_SYNCH,
|
||||||
};
|
};
|
||||||
|
|
||||||
class Thread : public KernelObject {
|
class Thread : public Kernel::Object {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
const char *GetName() { return name; }
|
const char *GetName() { return name; }
|
||||||
const char *GetTypeName() { return "Thread"; }
|
const char *GetTypeName() { return "Thread"; }
|
||||||
|
|
||||||
static KernelIDType GetStaticIDType() { return KERNEL_ID_TYPE_THREAD; }
|
static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Thread; }
|
||||||
KernelIDType GetIDType() const { return KERNEL_ID_TYPE_THREAD; }
|
Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Thread; }
|
||||||
|
|
||||||
inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; }
|
inline bool IsRunning() const { return (status & THREADSTATUS_RUNNING) != 0; }
|
||||||
inline bool IsStopped() const { return (status & THREADSTATUS_DORMANT) != 0; }
|
inline bool IsStopped() const { return (status & THREADSTATUS_DORMANT) != 0; }
|
||||||
@ -71,7 +71,7 @@ public:
|
|||||||
|
|
||||||
WaitType wait_type;
|
WaitType wait_type;
|
||||||
|
|
||||||
char name[KERNEL_MAX_NAME_LENGTH+1];
|
char name[Kernel::MAX_NAME_LENGTH + 1];
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lists all thread ids that aren't deleted/etc.
|
// Lists all thread ids that aren't deleted/etc.
|
||||||
@ -201,7 +201,7 @@ Thread *__KernelCreateThread(Handle &handle, const char *name, u32 entry_point,
|
|||||||
|
|
||||||
Thread *t = new Thread;
|
Thread *t = new Thread;
|
||||||
|
|
||||||
handle = g_kernel_objects.Create(t);
|
handle = Kernel::g_object_pool.Create(t);
|
||||||
|
|
||||||
g_thread_queue.push_back(handle);
|
g_thread_queue.push_back(handle);
|
||||||
g_thread_ready_queue.prepare(priority);
|
g_thread_ready_queue.prepare(priority);
|
||||||
@ -214,8 +214,8 @@ Thread *__KernelCreateThread(Handle &handle, const char *name, u32 entry_point,
|
|||||||
t->processor_id = processor_id;
|
t->processor_id = processor_id;
|
||||||
t->wait_type = WAITTYPE_NONE;
|
t->wait_type = WAITTYPE_NONE;
|
||||||
|
|
||||||
strncpy(t->name, name, KERNEL_MAX_NAME_LENGTH);
|
strncpy(t->name, name, Kernel::MAX_NAME_LENGTH);
|
||||||
t->name[KERNEL_MAX_NAME_LENGTH] = '\0';
|
t->name[Kernel::MAX_NAME_LENGTH] = '\0';
|
||||||
|
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ Thread *__KernelNextThread() {
|
|||||||
if (next < 0) {
|
if (next < 0) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
return g_kernel_objects.GetFast<Thread>(next);
|
return Kernel::g_object_pool.GetFast<Thread>(next);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets up the primary application thread
|
/// Sets up the primary application thread
|
||||||
@ -326,7 +326,7 @@ Handle __KernelSetupMainThread(s32 priority, int stack_size) {
|
|||||||
/// Resumes a thread from waiting by marking it as "ready"
|
/// Resumes a thread from waiting by marking it as "ready"
|
||||||
void __KernelResumeThreadFromWait(Handle handle) {
|
void __KernelResumeThreadFromWait(Handle handle) {
|
||||||
u32 error;
|
u32 error;
|
||||||
Thread *t = g_kernel_objects.Get<Thread>(handle, error);
|
Thread *t = Kernel::g_object_pool.Get<Thread>(handle, error);
|
||||||
if (t) {
|
if (t) {
|
||||||
t->status &= ~THREADSTATUS_WAIT;
|
t->status &= ~THREADSTATUS_WAIT;
|
||||||
if (!(t->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) {
|
if (!(t->status & (THREADSTATUS_WAITSUSPEND | THREADSTATUS_DORMANT | THREADSTATUS_DEAD))) {
|
||||||
|
@ -23,10 +23,10 @@ enum ThreadProcessorId {
|
|||||||
|
|
||||||
/// Creates a new thread - wrapper for external user
|
/// Creates a new thread - wrapper for external user
|
||||||
Handle __KernelCreateThread(const char *name, u32 entry_point, s32 priority,
|
Handle __KernelCreateThread(const char *name, u32 entry_point, s32 priority,
|
||||||
s32 processor_id, u32 stack_top, int stack_size=KERNEL_DEFAULT_STACK_SIZE);
|
s32 processor_id, u32 stack_top, int stack_size=Kernel::DEFAULT_STACK_SIZE);
|
||||||
|
|
||||||
/// Sets up the primary application thread
|
/// Sets up the primary application thread
|
||||||
Handle __KernelSetupMainThread(s32 priority, int stack_size=KERNEL_DEFAULT_STACK_SIZE);
|
Handle __KernelSetupMainThread(s32 priority, int stack_size=Kernel::DEFAULT_STACK_SIZE);
|
||||||
|
|
||||||
/// Reschedules to the next available thread (call after current thread is suspended)
|
/// Reschedules to the next available thread (call after current thread is suspended)
|
||||||
void __KernelReschedule(const char *reason);
|
void __KernelReschedule(const char *reason);
|
||||||
|
@ -34,7 +34,7 @@ Manager::~Manager() {
|
|||||||
|
|
||||||
/// Add a service to the manager (does not create it though)
|
/// Add a service to the manager (does not create it though)
|
||||||
void Manager::AddService(Interface* service) {
|
void Manager::AddService(Interface* service) {
|
||||||
m_port_map[service->GetPortName()] = g_kernel_objects.Create(service);
|
m_port_map[service->GetPortName()] = Kernel::g_object_pool.Create(service);
|
||||||
m_services.push_back(service);
|
m_services.push_back(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ void Manager::DeleteService(std::string port_name) {
|
|||||||
|
|
||||||
/// Get a Service Interface from its Handle
|
/// Get a Service Interface from its Handle
|
||||||
Interface* Manager::FetchFromHandle(Handle handle) {
|
Interface* Manager::FetchFromHandle(Handle handle) {
|
||||||
return g_kernel_objects.GetFast<Interface>(handle);
|
return Kernel::g_object_pool.GetFast<Interface>(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a Service Interface from its port
|
/// Get a Service Interface from its port
|
||||||
|
@ -36,15 +36,15 @@ inline static u32* GetCommandBuffer(const int offset=0) {
|
|||||||
class Manager;
|
class Manager;
|
||||||
|
|
||||||
/// Interface to a CTROS service
|
/// Interface to a CTROS service
|
||||||
class Interface : public KernelObject {
|
class Interface : public Kernel::Object {
|
||||||
friend class Manager;
|
friend class Manager;
|
||||||
public:
|
public:
|
||||||
|
|
||||||
const char *GetName() { return GetPortName(); }
|
const char *GetName() { return GetPortName(); }
|
||||||
const char *GetTypeName() { return GetPortName(); }
|
const char *GetTypeName() { return GetPortName(); }
|
||||||
|
|
||||||
static KernelIDType GetStaticIDType() { return KERNEL_ID_TYPE_THREAD; }
|
static Kernel::HandleType GetStaticHandleType() { return Kernel::HandleType::Service; }
|
||||||
KernelIDType GetIDType() const { return KERNEL_ID_TYPE_THREAD; }
|
Kernel::HandleType GetHandleType() const { return Kernel::HandleType::Service; }
|
||||||
|
|
||||||
typedef void (*Function)(Interface*);
|
typedef void (*Function)(Interface*);
|
||||||
|
|
||||||
@ -63,8 +63,8 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Allocates a new handle for the service
|
/// Allocates a new handle for the service
|
||||||
Handle CreateHandle(KernelObject *obj) {
|
Handle CreateHandle(Kernel::Object *obj) {
|
||||||
Handle handle = g_kernel_objects.Create(obj);
|
Handle handle = Kernel::g_object_pool.Create(obj);
|
||||||
m_handles.push_back(handle);
|
m_handles.push_back(handle);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ public:
|
|||||||
/// Frees a handle from the service
|
/// Frees a handle from the service
|
||||||
template <class T>
|
template <class T>
|
||||||
void DeleteHandle(const Handle handle) {
|
void DeleteHandle(const Handle handle) {
|
||||||
g_kernel_objects.Destroy<T>(handle);
|
g_object_pool.Destroy<T>(handle);
|
||||||
m_handles.erase(std::remove(m_handles.begin(), m_handles.end(), handle), m_handles.end());
|
m_handles.erase(std::remove(m_handles.begin(), m_handles.end(), handle), m_handles.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user