Browse Source

Get rid of pnUtSync and CLock.

Darryl Pogue 13 years ago
parent
commit
1ae3979753
  1. 1
      Sources/Plasma/NucleusLib/pnAsyncCoreExe/Pch.h
  2. 14
      Sources/Plasma/NucleusLib/pnAsyncCoreExe/pnAceIo.cpp
  3. 3
      Sources/Plasma/NucleusLib/pnUtils/CMakeLists.txt
  4. 60
      Sources/Plasma/NucleusLib/pnUtils/Unix/pnUtUxSync.cpp
  5. 316
      Sources/Plasma/NucleusLib/pnUtils/Win32/pnUtW32Sync.cpp
  6. 1
      Sources/Plasma/NucleusLib/pnUtils/pnUtAllIncludes.h
  7. 78
      Sources/Plasma/NucleusLib/pnUtils/pnUtSync.h
  8. 1
      Sources/Plasma/PubUtilLib/plNetGameLib/Pch.h
  9. 30
      Sources/Plasma/PubUtilLib/plNetGameLib/Private/plNglFile.cpp

1
Sources/Plasma/NucleusLib/pnAsyncCoreExe/Pch.h

@ -54,6 +54,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "pnProduct/pnProduct.h" #include "pnProduct/pnProduct.h"
#include "pnNetBase/pnNetBase.h" #include "pnNetBase/pnNetBase.h"
#include "pnAsyncCore/pnAsyncCore.h" #include "pnAsyncCore/pnAsyncCore.h"
#include "hsThread.h"
#include "Private/pnAceInt.h" #include "Private/pnAceInt.h"
#include "Private/W9x/pnAceW9x.h" #include "Private/W9x/pnAceW9x.h"

14
Sources/Plasma/NucleusLib/pnAsyncCoreExe/pnAceIo.cpp

@ -83,7 +83,7 @@ struct ISocketConnType : ISocketConnHash {
}; };
static CLock s_notifyProcLock; static hsReaderWriterLock s_notifyProcLock;
static HASHTABLEDECL( static HASHTABLEDECL(
ISocketConnType, ISocketConnType,
ISocketConnHash, ISocketConnHash,
@ -340,11 +340,11 @@ void AsyncSocketRegisterNotifyProc (
ct->productId = productId; ct->productId = productId;
ct->flags = kConnHashFlagsIgnore; ct->flags = kConnHashFlagsIgnore;
s_notifyProcLock.EnterWrite(); s_notifyProcLock.LockForWriting();
{ {
s_notifyProcs.Add(ct); s_notifyProcs.Add(ct);
} }
s_notifyProcLock.LeaveWrite(); s_notifyProcLock.UnlockForWriting();
} }
//=========================================================================== //===========================================================================
@ -365,7 +365,7 @@ void AsyncSocketUnregisterNotifyProc (
hash.flags = kConnHashFlagsExactMatch; hash.flags = kConnHashFlagsExactMatch;
ISocketConnType * scan; ISocketConnType * scan;
s_notifyProcLock.EnterWrite(); s_notifyProcLock.LockForWriting();
{ {
scan = s_notifyProcs.Find(hash); scan = s_notifyProcs.Find(hash);
for (; scan; scan = s_notifyProcs.FindNext(hash, scan)) { for (; scan; scan = s_notifyProcs.FindNext(hash, scan)) {
@ -377,7 +377,7 @@ void AsyncSocketUnregisterNotifyProc (
break; break;
} }
} }
s_notifyProcLock.LeaveWrite(); s_notifyProcLock.UnlockForWriting();
// perform memory deallocation outside the lock // perform memory deallocation outside the lock
delete scan; delete scan;
@ -403,12 +403,12 @@ FAsyncNotifySocketProc AsyncSocketFindNotifyProc (
// Lookup notifyProc based on connType // Lookup notifyProc based on connType
FAsyncNotifySocketProc proc; FAsyncNotifySocketProc proc;
s_notifyProcLock.EnterRead(); s_notifyProcLock.LockForReading();
if (const ISocketConnType * scan = s_notifyProcs.Find(hash)) if (const ISocketConnType * scan = s_notifyProcs.Find(hash))
proc = scan->notifyProc; proc = scan->notifyProc;
else else
proc = nil; proc = nil;
s_notifyProcLock.LeaveRead(); s_notifyProcLock.UnlockForReading();
if (!proc) if (!proc)
break; break;

3
Sources/Plasma/NucleusLib/pnUtils/CMakeLists.txt

@ -18,7 +18,6 @@ set(pnUtils_HEADERS
pnUtRef.h pnUtRef.h
pnUtSort.h pnUtSort.h
pnUtStr.h pnUtStr.h
pnUtSync.h
pnUtTime.h pnUtTime.h
pnUtUuid.h pnUtUuid.h
) )
@ -41,14 +40,12 @@ if(WIN32)
Win32/pnUtW32Misc.cpp Win32/pnUtW32Misc.cpp
Win32/pnUtW32Path.cpp Win32/pnUtW32Path.cpp
Win32/pnUtW32Str.cpp Win32/pnUtW32Str.cpp
Win32/pnUtW32Sync.cpp
Win32/pnUtW32Time.cpp Win32/pnUtW32Time.cpp
Win32/pnUtW32Uuid.cpp Win32/pnUtW32Uuid.cpp
) )
else() else()
set(pnUtils_UNIX set(pnUtils_UNIX
Unix/pnUtUxStr.cpp Unix/pnUtUxStr.cpp
#Unix/pnUtUxSync.cpp
Unix/pnUtUxUuid.cpp Unix/pnUtUxUuid.cpp
) )
endif() endif()

60
Sources/Plasma/NucleusLib/pnUtils/Unix/pnUtUxSync.cpp

@ -1,60 +0,0 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/NucleusLib/pnUtils/Private/Unix/pnUtUxSync.cpp
*
***/
#include "../pnUtSync.h"
#ifdef HS_BUILD_FOR_UNIX
#else
// Dummy function to prevent a linker warning complaining about no public symbols if the
// contents of the file get compiled out via pre-processor
void UxSyncPreventLNK4221Warning () {
}
#endif

316
Sources/Plasma/NucleusLib/pnUtils/Win32/pnUtW32Sync.cpp

@ -1,316 +0,0 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/NucleusLib/pnUtils/Private/Win32/pnUtW32Sync.cpp
*
***/
#include "../pnUtils.h"
/****************************************************************************
*
* Spin lock functions
*
***/
//===========================================================================
static inline void EnterSpinLock (long * spinLock) {
for (;;)
if (*spinLock < 0)
if (!InterlockedIncrement(spinLock))
return;
else
InterlockedDecrement(spinLock);
}
//===========================================================================
static inline void LeaveSpinLock (long * spinLock) {
InterlockedDecrement(spinLock);
}
/****************************************************************************
*
* CLockWaitSet / CLockWaitSetAllocator
*
***/
class CLockWaitSet {
private:
unsigned m_refCount;
HANDLE m_waitEvent;
public:
LINK(CLockWaitSet) link;
inline CLockWaitSet ();
inline ~CLockWaitSet ();
inline void DecRef ();
inline void IncRef ();
inline void Signal ();
inline void Wait ();
};
class CLockWaitSetAllocator {
private:
CLockWaitSet m_array[256];
CLockWaitSetAllocator * m_prev;
LISTDECL(CLockWaitSet, link) m_spareList;
LISTDECL(CLockWaitSet, link) m_usedList;
static CLockWaitSetAllocator * s_allocator;
static long s_spinLock;
public:
CLockWaitSetAllocator (CLockWaitSetAllocator * prev);
~CLockWaitSetAllocator ();
static CLockWaitSet * Alloc ();
static void Free (CLockWaitSet * waitSet);
static void __cdecl Shutdown ();
};
CLockWaitSetAllocator * CLockWaitSetAllocator::s_allocator;
long CLockWaitSetAllocator::s_spinLock = -1;
//===========================================================================
CLockWaitSet::CLockWaitSet () {
m_refCount = 0;
m_waitEvent = CreateEvent(nil, true, false, nil);
}
//===========================================================================
CLockWaitSet::~CLockWaitSet () {
ASSERT(!m_refCount);
CloseHandle(m_waitEvent);
m_waitEvent = 0;
}
//===========================================================================
void CLockWaitSet::DecRef () {
ASSERT(m_refCount);
if (!--m_refCount) {
ResetEvent(m_waitEvent);
CLockWaitSetAllocator::Free(this);
}
}
//===========================================================================
void CLockWaitSet::IncRef () {
++m_refCount;
}
//===========================================================================
void CLockWaitSet::Signal () {
ASSERT(m_refCount);
SetEvent(m_waitEvent);
}
//===========================================================================
void CLockWaitSet::Wait () {
ASSERT(m_refCount);
WaitForSingleObject(m_waitEvent, INFINITE);
}
//===========================================================================
CLockWaitSetAllocator::CLockWaitSetAllocator (CLockWaitSetAllocator * prev) {
m_prev = prev;
if (prev) {
m_spareList.Link(&prev->m_spareList);
m_usedList.Link(&prev->m_usedList);
}
for (unsigned index = arrsize(m_array); index--; )
m_spareList.Link(&m_array[index]);
}
//===========================================================================
CLockWaitSetAllocator::~CLockWaitSetAllocator () {
delete m_prev;
}
//===========================================================================
CLockWaitSet * CLockWaitSetAllocator::Alloc () {
EnterSpinLock(&s_spinLock);
// If there is no active allocator or if the active allocator is full,
// create a new one
if (!s_allocator || !s_allocator->m_spareList.Head()) {
if (!s_allocator)
atexit(Shutdown);
s_allocator = new CLockWaitSetAllocator(s_allocator);
}
// Get an available wait set from the active allocator
CLockWaitSet * waitSet = s_allocator->m_spareList.Head();
s_allocator->m_usedList.Link(waitSet);
LeaveSpinLock(&s_spinLock);
return waitSet;
}
//===========================================================================
void CLockWaitSetAllocator::Free (CLockWaitSet * waitSet) {
EnterSpinLock(&s_spinLock);
// Return this wait set to the active allocator's spare list
ASSERT(s_allocator);
s_allocator->m_spareList.Link(waitSet);
LeaveSpinLock(&s_spinLock);
}
//===========================================================================
void CLockWaitSetAllocator::Shutdown () {
EnterSpinLock(&s_spinLock);
// Free all allocators
while (s_allocator) {
CLockWaitSetAllocator * prev = s_allocator->m_prev;
delete s_allocator;
s_allocator = prev;
}
LeaveSpinLock(&s_spinLock);
}
/****************************************************************************
*
* CLock
*
***/
//===========================================================================
CLock::CLock () {
m_waitSet = nil;
m_spinLock = -1;
m_readerCount = 0;
m_writerCount = 0;
}
//===========================================================================
CLock::~CLock () {
ASSERT(!m_waitSet);
ASSERT(m_spinLock == -1);
ASSERT(!m_readerCount);
ASSERT(!m_writerCount);
}
//===========================================================================
void CLock::EnterRead () {
EnterSpinLock(&m_spinLock);
for (;;) {
// If there are no writers, claim this lock for reading
if (!m_writerCount) {
++m_readerCount;
break;
}
// Otherwise, wait until the existing writer releases the lock
CLockWaitSet * waitSet = m_waitSet = (m_waitSet ? m_waitSet : CLockWaitSetAllocator::Alloc());
waitSet->IncRef();
LeaveSpinLock(&m_spinLock);
waitSet->Wait();
EnterSpinLock(&m_spinLock);
waitSet->DecRef();
}
LeaveSpinLock(&m_spinLock);
}
//===========================================================================
void CLock::EnterWrite () {
EnterSpinLock(&m_spinLock);
for (;;) {
// If there are no readers or writers, claim this lock for writing
if (!m_readerCount && !m_writerCount) {
++m_writerCount;
break;
}
// Otherwise, wait until the existing writer or all existing readers
// release the lock
CLockWaitSet * waitSet = m_waitSet = (m_waitSet ? m_waitSet : CLockWaitSetAllocator::Alloc());
waitSet->IncRef();
LeaveSpinLock(&m_spinLock);
waitSet->Wait();
EnterSpinLock(&m_spinLock);
waitSet->DecRef();
}
LeaveSpinLock(&m_spinLock);
}
//===========================================================================
void CLock::LeaveRead () {
EnterSpinLock(&m_spinLock);
// If this is the last reader, signal waiting threads to try claiming
// the lock again
ASSERT(m_readerCount);
if (!--m_readerCount)
if (m_waitSet) {
m_waitSet->Signal();
m_waitSet = nil;
}
LeaveSpinLock(&m_spinLock);
}
//===========================================================================
void CLock::LeaveWrite () {
EnterSpinLock(&m_spinLock);
// This is the last writer. Signal waiting threads to try claiming the
// lock again.
ASSERT(m_writerCount == 1);
--m_writerCount;
if (m_waitSet) {
m_waitSet->Signal();
m_waitSet = nil;
}
LeaveSpinLock(&m_spinLock);
}

1
Sources/Plasma/NucleusLib/pnUtils/pnUtAllIncludes.h

@ -57,7 +57,6 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "pnUtList.h" #include "pnUtList.h"
#include "pnUtHash.h" #include "pnUtHash.h"
#include "pnUtPriQ.h" #include "pnUtPriQ.h"
#include "pnUtSync.h"
#include "pnUtTime.h" #include "pnUtTime.h"
#include "pnUtStr.h" #include "pnUtStr.h"
#include "pnUtRef.h" #include "pnUtRef.h"

78
Sources/Plasma/NucleusLib/pnUtils/pnUtSync.h

@ -1,78 +0,0 @@
/*==LICENSE==*
CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011 Cyan Worlds, Inc.
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
Additional permissions under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.
You can contact Cyan Worlds, Inc. by email legal@cyan.com
or by snail mail at:
Cyan Worlds, Inc.
14617 N Newport Hwy
Mead, WA 99021
*==LICENSE==*/
/*****************************************************************************
*
* $/Plasma20/Sources/Plasma/NucleusLib/pnUtils/Private/pnUtSync.h
*
***/
#ifndef PLASMA20_SOURCES_PLASMA_NUCLEUSLIB_PNUTILS_PRIVATE_PNUTSYNC_H
#define PLASMA20_SOURCES_PLASMA_NUCLEUSLIB_PNUTILS_PRIVATE_PNUTSYNC_H
#include "Pch.h"
/****************************************************************************
*
* CLock
* (reader/writer lock)
*
***/
class CLockWaitSet;
class CLock {
private:
CLockWaitSet * m_waitSet;
long m_spinLock;
unsigned m_readerCount;
unsigned m_writerCount;
public:
CLock ();
~CLock ();
void EnterRead ();
void EnterWrite ();
void LeaveRead ();
void LeaveWrite ();
};
#endif

1
Sources/Plasma/PubUtilLib/plNetGameLib/Pch.h

@ -56,6 +56,7 @@ You can contact Cyan Worlds, Inc. by email legal@cyan.com
#include "pnAsyncCore/pnAsyncCore.h" #include "pnAsyncCore/pnAsyncCore.h"
#include "pnNetCli/pnNetCli.h" #include "pnNetCli/pnNetCli.h"
#include "pnProduct/pnProduct.h" #include "pnProduct/pnProduct.h"
#include "hsThread.h"
#define USES_PROTOCOL_CLI2AUTH #define USES_PROTOCOL_CLI2AUTH
#define USES_PROTOCOL_CLI2GAME #define USES_PROTOCOL_CLI2GAME

30
Sources/Plasma/PubUtilLib/plNetGameLib/Private/plNglFile.cpp

@ -62,7 +62,7 @@ namespace Ngl { namespace File {
struct CliFileConn : AtomicRef { struct CliFileConn : AtomicRef {
LINK(CliFileConn) link; LINK(CliFileConn) link;
CLock sockLock; // to protect the socket pointer so we don't nuke it while using it hsReaderWriterLock sockLock; // to protect the socket pointer so we don't nuke it while using it
AsyncSocket sock; AsyncSocket sock;
char name[MAX_PATH]; char name[MAX_PATH];
plNetAddress addr; plNetAddress addr;
@ -283,12 +283,12 @@ static void UnlinkAndAbandonConn_CS (CliFileConn * conn) {
needsDecref = false; needsDecref = false;
} }
else { else {
conn->sockLock.EnterRead(); conn->sockLock.LockForReading();
if (conn->sock) { if (conn->sock) {
AsyncSocketDisconnect(conn->sock, true); AsyncSocketDisconnect(conn->sock, true);
needsDecref = false; needsDecref = false;
} }
conn->sockLock.LeaveRead(); conn->sockLock.UnlockForReading();
} }
if (needsDecref) { if (needsDecref) {
conn->DecRef("Lifetime"); conn->DecRef("Lifetime");
@ -311,9 +311,9 @@ static void NotifyConnSocketConnect (CliFileConn * conn) {
} }
else else
{ {
conn->sockLock.EnterRead(); conn->sockLock.LockForReading();
AsyncSocketDisconnect(conn->sock, true); AsyncSocketDisconnect(conn->sock, true);
conn->sockLock.LeaveRead(); conn->sockLock.UnlockForReading();
} }
} }
s_critsect.Leave(); s_critsect.Leave();
@ -468,9 +468,9 @@ static bool SocketNotifyCallback (
*userState = conn; *userState = conn;
s_critsect.Enter(); s_critsect.Enter();
{ {
conn->sockLock.EnterWrite(); conn->sockLock.LockForWriting();
conn->sock = sock; conn->sock = sock;
conn->sockLock.LeaveWrite(); conn->sockLock.UnlockForWriting();
conn->cancelId = 0; conn->cancelId = 0;
} }
s_critsect.Leave(); s_critsect.Leave();
@ -690,9 +690,9 @@ void CliFileConn::AutoPing () {
IncRef("PingTimer"); IncRef("PingTimer");
timerCritsect.Enter(); timerCritsect.Enter();
{ {
sockLock.EnterRead(); sockLock.LockForReading();
unsigned timerPeriod = sock ? 0 : kAsyncTimeInfinite; unsigned timerPeriod = sock ? 0 : kAsyncTimeInfinite;
sockLock.LeaveRead(); sockLock.UnlockForReading();
AsyncTimerCreate( AsyncTimerCreate(
&pingTimer, &pingTimer,
@ -718,7 +718,7 @@ void CliFileConn::StopAutoPing () {
//============================================================================ //============================================================================
void CliFileConn::TimerPing () { void CliFileConn::TimerPing () {
sockLock.EnterRead(); sockLock.LockForReading();
for (;;) { for (;;) {
if (!sock) // make sure it exists if (!sock) // make sure it exists
break; break;
@ -745,18 +745,18 @@ void CliFileConn::TimerPing () {
} }
break; break;
} }
sockLock.LeaveRead(); sockLock.UnlockForReading();
} }
//============================================================================ //============================================================================
void CliFileConn::Destroy () { void CliFileConn::Destroy () {
AsyncSocket oldSock = nil; AsyncSocket oldSock = nil;
sockLock.EnterWrite(); sockLock.LockForWriting();
{ {
SWAP(oldSock, sock); SWAP(oldSock, sock);
} }
sockLock.LeaveWrite(); sockLock.UnlockForWriting();
if (oldSock) if (oldSock)
AsyncSocketDelete(oldSock); AsyncSocketDelete(oldSock);
@ -765,11 +765,11 @@ void CliFileConn::Destroy () {
//============================================================================ //============================================================================
void CliFileConn::Send (const void * data, unsigned bytes) { void CliFileConn::Send (const void * data, unsigned bytes) {
sockLock.EnterRead(); sockLock.LockForReading();
if (sock) { if (sock) {
AsyncSocketSend(sock, data, bytes); AsyncSocketSend(sock, data, bytes);
} }
sockLock.LeaveRead(); sockLock.UnlockForReading();
} }
//============================================================================ //============================================================================

Loading…
Cancel
Save