From ea20f76c5427a653d2afee9a89240720cbde6d66 Mon Sep 17 00:00:00 2001 From: Jhobean <51728381+Jhobean@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:00:51 -0500 Subject: [PATCH 1/2] Fix issue #1097 (CAN_O_NOSLEEP not work on server reboot) (#1198) --- src/game/CSector.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/game/CSector.cpp b/src/game/CSector.cpp index 866ed24e3..ff7c954e0 100644 --- a/src/game/CSector.cpp +++ b/src/game/CSector.cpp @@ -949,7 +949,8 @@ void CSector::MoveItemToSector( CItem * pItem ) { if (_CanSleep(true)) { - pItem->GoSleep(); + if (!pItem->CanTick()) + pItem->GoSleep(); } else { @@ -1011,7 +1012,8 @@ bool CSector::MoveCharToSector( CChar * pChar ) } else if (!pChar->IsSleeping()) // An NPC entered, but the sector is sleeping { - pChar->GoSleep(); // then make the NPC sleep too. + if (!pChar->CanTick()) + pChar->GoSleep(); // then make the NPC sleep too. } } else From a5d57147d968bd4912853ecdc76a8f2032774441 Mon Sep 17 00:00:00 2001 From: cbnolok Date: Wed, 13 Dec 2023 19:30:30 +0100 Subject: [PATCH 2/2] Fixed: Rare crash occurring when a NPC is selecting an attackable target, but there's only one target (not attackable) in sight. (#1193) --- Changelog.txt | 3 + src/game/CServerConfig.cpp | 2 +- src/game/CTeleport.cpp | 5 -- src/game/CTeleport.h | 2 +- src/game/chars/CChar.h | 4 +- src/game/chars/CCharFight.cpp | 5 +- src/game/chars/CCharNPCAct_Fight.cpp | 22 ++++++-- src/game/chars/CCharNPCStatus.cpp | 2 +- src/network/linuxev.cpp | 83 ++++++++++++++++++++++------ src/network/linuxev.h | 20 +++---- 10 files changed, 101 insertions(+), 47 deletions(-) diff --git a/Changelog.txt b/Changelog.txt index c8a0c951a..4e85ee106 100644 --- a/Changelog.txt +++ b/Changelog.txt @@ -3438,3 +3438,6 @@ Additionally, the problem of zig-zag issue following in the South direction has 09-12-2023, DavideRei - Added: DispID can now also be used on chars, to change their appearance. + +13-12-2023, Nolok +- Fixed: Rare crash occurring when a NPC is selecting an attackable target, but there's only one target (not attackable) in sight. diff --git a/src/game/CServerConfig.cpp b/src/game/CServerConfig.cpp index 595adfb93..bb879b969 100644 --- a/src/game/CServerConfig.cpp +++ b/src/game/CServerConfig.cpp @@ -4441,7 +4441,7 @@ bool CServerConfig::LoadIni( bool fTest ) if( !fTest ) { g_Log.Event(LOGL_FATAL|LOGM_INIT|LOGF_CONSOLE_ONLY, SPHERE_FILE ".ini has not been found.\n"); - g_Log.Event(LOGL_FATAL|LOGM_INIT|LOGF_CONSOLE_ONLY, "Download a sample sphere.ini from https://github.com/Sphereserver/Source-experimental/tree/master/src\n"); + g_Log.Event(LOGL_FATAL|LOGM_INIT|LOGF_CONSOLE_ONLY, "Download a sample sphere.ini from https://github.com/Sphereserver/Source-X/tree/master/src\n"); } return false; } diff --git a/src/game/CTeleport.cpp b/src/game/CTeleport.cpp index 48a25aa99..1b07a7193 100644 --- a/src/game/CTeleport.cpp +++ b/src/game/CTeleport.cpp @@ -42,8 +42,3 @@ bool CTeleport::RealizeTeleport() else return false; } - -CTeleport::~CTeleport() noexcept -{ - fprintf(stderr, "deleted %s.\n", _ptDst.WriteUsed()); -} diff --git a/src/game/CTeleport.h b/src/game/CTeleport.h index 0c0476115..6a566304a 100644 --- a/src/game/CTeleport.h +++ b/src/game/CTeleport.h @@ -21,7 +21,7 @@ class CTeleport : public CPointSort // The static world teleporters. _fNpc = false; } explicit CTeleport(tchar* pszArgs); - ~CTeleport() noexcept; + ~CTeleport() noexcept = default; CTeleport(const CTeleport& copy) = delete; CTeleport& operator=(const CTeleport& other) = delete; diff --git a/src/game/chars/CChar.h b/src/game/chars/CChar.h index b6901d1ee..0b8fc7029 100644 --- a/src/game/chars/CChar.h +++ b/src/game/chars/CChar.h @@ -1044,7 +1044,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; SKILL_TYPE Fight_GetWeaponSkill() const; DAMAGE_TYPE Fight_GetWeaponDamType(const CItem* pWeapon = nullptr) const; int Fight_CalcDamage( const CItem * pWeapon, bool bNoRandom = false, bool bGetMax = true ) const; - bool Fight_IsAttackable(); + bool Fight_IsAttackableState(); // Attacker System enum ATTACKER_CLEAR_TYPE @@ -1243,7 +1243,7 @@ public: void StatFlag_Mod(uint64 uiStatFlag, bool fMod) noexcept; bool NPC_LookAtItem( CItem * pItem, int iDist ); bool NPC_LookAround( bool fForceCheckItems = false ); int NPC_WalkToPoint(bool fRun = false); - CChar * NPC_FightFindBestTarget(); + CChar * NPC_FightFindBestTarget(const std::vector * pvExcludeList = nullptr); bool NPC_FightMagery(CChar * pChar); bool NPC_FightCast(CObjBase * &pChar ,CObjBase * pSrc, SPELL_TYPE &spell, int &skill, int iHealThreshold, bool bIgnoreAITargetChoice = false); bool NPC_FightArchery( CChar * pChar ); diff --git a/src/game/chars/CCharFight.cpp b/src/game/chars/CCharFight.cpp index cb1f61a52..2e3e967fa 100644 --- a/src/game/chars/CCharFight.cpp +++ b/src/game/chars/CCharFight.cpp @@ -1208,7 +1208,7 @@ int CChar::Fight_CalcDamage( const CItem * pWeapon, bool bNoRandom, bool bGetMax return( Calc_GetRandVal2(iDmgMin, iDmgMax) ); } -bool CChar::Fight_IsAttackable() +bool CChar::Fight_IsAttackableState() { ADDTOCALLSTACK("CChar::IsAttackable"); return !IsDisconnected() && !IsStatFlag(STATF_DEAD|STATF_STONE|STATF_INVISIBLE|STATF_INSUBSTANTIAL|STATF_HIDDEN|STATF_INVUL); @@ -1383,7 +1383,8 @@ void CChar::Fight_HitTry() // I can't hit this target, try switch to another one if (m_pNPC) { - if ( !Fight_Attack(NPC_FightFindBestTarget()) ) + std::vector vExcludeTargets { pCharTarg }; // Ignore the current target, i want other npcs + if (!Fight_Attack(NPC_FightFindBestTarget(&vExcludeTargets))) { Skill_Start(SKILL_NONE); m_Fight_Targ_UID.InitUID(); diff --git a/src/game/chars/CCharNPCAct_Fight.cpp b/src/game/chars/CCharNPCAct_Fight.cpp index ad6224335..2b7e3d589 100644 --- a/src/game/chars/CCharNPCAct_Fight.cpp +++ b/src/game/chars/CCharNPCAct_Fight.cpp @@ -54,7 +54,7 @@ bool CChar::NPC_FightArchery(CChar * pChar) return true; } -CChar * CChar::NPC_FightFindBestTarget() +CChar * CChar::NPC_FightFindBestTarget(const std::vector* pvExcludeList) { ADDTOCALLSTACK("CChar::NPC_FightFindBestTarget"); ASSERT(m_pNPC); @@ -74,17 +74,26 @@ CChar * CChar::NPC_FightFindBestTarget() for (size_t i = 0; i < m_lastAttackers.size(); ) { LastAttackers &refAttacker = m_lastAttackers[i]; - pChar = CUID(refAttacker.charUID).CharFind(); + pChar = CUID::CharFindFromUID(refAttacker.charUID); if (!pChar) { ++i; continue; } - if (!pChar->Fight_IsAttackable()) + if (!pChar->Fight_IsAttackableState()) { ++i; continue; } + if (pvExcludeList) + { + if (pvExcludeList->cend() != std::find(pvExcludeList->cbegin(), pvExcludeList->cend(), pChar)) + { + ++i; + continue; + } + } + if (refAttacker.ignore) { bool bIgnore = true; @@ -140,10 +149,13 @@ CChar * CChar::NPC_FightFindBestTarget() return pClosest ? pClosest : pChar; } - // New target not found, check if I can keep attacking my current target + // New target not found, return the current target, if any CChar *pTarget = m_Fight_Targ_UID.CharFind(); if (pTarget) - return pTarget; + { + if (!pvExcludeList || (pvExcludeList->cend() == std::find(pvExcludeList->cbegin(), pvExcludeList->cend(), pTarget))) + return pTarget; + } return nullptr; } diff --git a/src/game/chars/CCharNPCStatus.cpp b/src/game/chars/CCharNPCStatus.cpp index 7c8c07ecd..1c5a307af 100644 --- a/src/game/chars/CCharNPCStatus.cpp +++ b/src/game/chars/CCharNPCStatus.cpp @@ -849,7 +849,7 @@ int CChar::NPC_GetAttackContinueMotivation( CChar * pChar, int iMotivation ) con if ( !pChar ) return 0; - if ( !pChar->Fight_IsAttackable() ) + if ( !pChar->Fight_IsAttackableState() ) return -100; if ( m_pNPC->m_Brain == NPCBRAIN_GUARD ) return 100; diff --git a/src/network/linuxev.cpp b/src/network/linuxev.cpp index 518cab3df..943945587 100644 --- a/src/network/linuxev.cpp +++ b/src/network/linuxev.cpp @@ -26,18 +26,23 @@ } */ -// Call this function when the socket is readable again -> we are not sending data anymore -// The data is sent (if the checks are passing) at each tick on a CNetworkThread, which sets also isSendingAsync to true. If in that tick -// the CNetworkThread can't send the data (maybe because socketslave_cb wasn't called, so onAsyncSendComplete wasn't called), wait for the next tick. -static void socketslave_cb(struct ev_loop *loop, struct ev_io *w, int revents) +// Call this callback function when the socket is readable or writable again -> we are not sending data anymore +// The data is sent when there's some queued data to send. +static void socketslave_cb(struct ev_loop *loop, struct ev_io *watcher, int revents) { - ev_io_stop(loop, w); - CNetState* state = reinterpret_cast( w->data ); + // libev could call this function aliasing ev_io as a ev_watcher. + // ev_watcher is a "parent" struct of ev_io, they share the first member variables. + // it's a evil trick, but does the job since C doesn't have struct inheritance + + ev_io_stop(loop, watcher); + CNetState* state = reinterpret_cast( watcher->data ); if ( !g_Serv.IsLoading() ) { if ( revents & EV_READ ) { + // This happens when the client socket is readable (i can try to retrieve data), this does NOT mean + // that i have data to read. It might also mean that i have done writing to the socket? // g_NetworkOut.onAsyncSendComplete(state); } else if ( revents & EV_WRITE ) @@ -48,19 +53,19 @@ static void socketslave_cb(struct ev_loop *loop, struct ev_io *w, int revents) } } - if ( state->isSendingAsync() ) + if (state->isSendingAsync()) { - ev_io_start(loop, w); + ev_io_start(loop, watcher); } } -LinuxEv::LinuxEv(void) : AbstractSphereThread("T_NetLoop", IThread::High) +LinuxEv::LinuxEv(void) : AbstractSphereThread("T_NetLoopOut", IThread::High) + //, m_watchMainsock{} { - m_eventLoop = ev_loop_new(EV_BACKEND_LIST); - ASSERT(m_eventLoop != nullptr); // libev probably couldn't find sys/poll.h, select.h and other includes (compiling on ubuntu with both x86_64 and i386 compilers? or more gcc versions?) - ev_set_io_collect_interval(m_eventLoop, 0.01); - - memset(&m_watchMainsock, 0, sizeof(ev_io)); + // Right now, we use libev to send asynchronously packets to clients. + m_eventLoop = ev_loop_new(ev_recommended_backends() | EVFLAG_NOENV); + ASSERT(m_eventLoop != nullptr); // if fails, libev config.h probably was not configured properly + ev_set_io_collect_interval(m_eventLoop, 5e-3); // interval: the second is the unit, use decimals for smaller intervals } LinuxEv::~LinuxEv(void) @@ -68,15 +73,52 @@ LinuxEv::~LinuxEv(void) ev_loop_destroy(m_eventLoop); } +void LinuxEv::printInitInfo() +{ + g_Log.Event(LOGM_CLIENTS_LOG, "Networking: Libev. Initialized with backend 0x%x.\n", ev_backend(m_eventLoop)); +} + void LinuxEv::onStart() { - // g_Log.Event(LOGM_CLIENTS_LOG, "Event start backend 0x%x\n", ev_backend(m_eventLoop)); AbstractSphereThread::onStart(); } - + +static void periodic_cb(struct ev_loop* /*loop*/, ev_periodic* /*w*/, int /*revents*/) noexcept +{ + ; +} + void LinuxEv::tick() { - ev_run(m_eventLoop, EVRUN_NOWAIT); + /* + A flags value of EVRUN_NOWAIT will look for new events, + will handle those events and any already outstanding ones, + but will not wait and block your process in case there are no events and will return after one iteration of the loop. + */ + //ev_run(m_eventLoop, EVRUN_NOWAIT); + + // Trying a different approach: enter the event loop, run again if exits. + // ev_run will keep handling events until either no event watchers are active anymore or "ev_break" was called + +#ifdef _DEBUG + g_Log.EventDebug("Networking: Libev. Starting event loop.\n"); +#endif + + // This periodic timer keeps awake the event loop. We could have used ev_ref but it had its problems... + // Don't ask me why (maybe i don't get how this actually should work, and this is only a workaround), + // but if we rely on ev_ref to increase the event loop reference counter to keep it alive without this periodic timer/callback, + // the loop will ignore the io_collect_interval. Moreover, it will make the polling backend in use (like most frequently epoll) wait the maximum + // time (MAX_BLOCKTIME in ev.c, circa 60 seconds) to collect incoming data, only then the callback will be called. So each batch of packets + // would be processed every 60 seconds... + struct ev_periodic periodic_check; + ev_periodic_init(&periodic_check, periodic_cb, 0, 5e-3, nullptr); + ev_periodic_start(m_eventLoop, &periodic_check); + + ev_run(m_eventLoop, 0); + +#ifdef _DEBUG + g_Log.EventDebug("Networking: Libev. Event loop STOPPED.\n"); +#endif } void LinuxEv::waitForClose() @@ -96,6 +138,13 @@ void LinuxEv::registerClient(CNetState * state, EventsID eventCheck) memset(state->iocb(), 0, sizeof(ev_io)); + // Right now we support only async writing to the socket. + // Pure async read would mean to call functions and access data typically managed by the main thread, + // but the core isn't designed for such usage, nor we have all the thread synchronization methods for every possible stuff we might need. + // A fair compromise TODO would be to async read incoming data, parse that in packets that will be stored in a buffer periodically accessed and processed + // by the main thread. + ASSERT(0 == (eventCheck & ~EV_WRITE)); + #ifdef _WIN32 int fd = EV_WIN32_HANDLE_TO_FD(state->m_socket.GetSocket()); ev_io_init(state->iocb(), socketslave_cb, fd, (int)eventCheck); diff --git a/src/network/linuxev.h b/src/network/linuxev.h index fcda6147d..728472893 100644 --- a/src/network/linuxev.h +++ b/src/network/linuxev.h @@ -11,12 +11,6 @@ #include "../../lib/libev/src/ev.h" #include "../common/sphere_library/smutex.h" #include "../sphere/threads.h" - - #ifdef _BSD - #define EV_BACKEND_LIST (EVBACKEND_SELECT | EVBACKEND_POLL | EVBACKEND_KQUEUE) - #else - #define EV_BACKEND_LIST (EVBACKEND_SELECT | EVBACKEND_POLL | EVBACKEND_EPOLL) - #endif class CClient; class CNetState; @@ -36,25 +30,25 @@ private: struct ev_loop * m_eventLoop; - struct ev_io m_watchMainsock; + // struct ev_io m_watchMainsock; // Watcher for Sphere's socket, to accept incoming connections (async read). public: LinuxEv(void); virtual ~LinuxEv(void); - private: - LinuxEv(const LinuxEv& copy); - LinuxEv& operator=(const LinuxEv& other); + LinuxEv(const LinuxEv& copy) = delete; + LinuxEv& operator=(const LinuxEv& other) = delete; public: - virtual void onStart(); - virtual void tick(); - virtual void waitForClose(); + virtual void onStart() override; + virtual void tick() override; + virtual void waitForClose() override; private: void forceClientevent(CNetState *, EventsID); public: + void printInitInfo(); void forceClientread(CNetState *); void forceClientwrite(CNetState *); // --------------------------------------