native: perf telemetry, 2 frames in flight, 120 Hz pacing, GPU shadow map
- --perf-log CSV + logcat telemetry (perf_log.h, EterBase/PerfCounters.h), incl. display refresh rate / app vsync rate, thermal, CPU clocks; tools/pull_perf_logs.sh + tools/perf_summary.py - Vulkan renderer: two frames in flight, in-frame texture uploads (no vkQueueWaitIdle), --fps-cap / --refresh-rate / --frames-in-flight - Android: ANativeWindow_setFrameRate, ADPF performance hint, appCategory=game (ColorOS no longer drops to 60 Hz when idle) - Character shadow map rendered on the GPU into an offscreen render target instead of CPU rasterization + per-frame 1 MB upload; --cpu-shadow keeps the old path. Device (120 Hz, 32 mobs): 75 -> ~115 fps, script thread 8.9 -> 3.4 ms. draw_capture format v10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
3e3708ef06
commit
c2cc093ed0
@@ -0,0 +1,124 @@
|
||||
#pragma once
|
||||
// PORT: performance telemetry (40250 has no counterpart). The script thread adds the wall time of the
|
||||
// sections of CPythonApplication::Process() and of the Python callbacks here; the native host
|
||||
// (native_render/perf_log.h) reads and clears the totals every sample window. The two threads never run
|
||||
// at the same time (PythonBoot hands one baton between them), so plain fields are enough.
|
||||
//
|
||||
// Nothing is timed until MtPerf::Enabled() is set, so a build without --perf-log pays one branch per
|
||||
// section.
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace MtPerf
|
||||
{
|
||||
enum ESection
|
||||
{
|
||||
SECTION_PROCESS, // the whole CPythonApplication::Process()
|
||||
SECTION_NETWORK, // CPythonNetworkStream + CAccountConnector Process
|
||||
SECTION_CAMERA, // __UpdateCamera, CResourceManager::Update, OnCameraUpdate, OnMouseUpdate
|
||||
SECTION_UI_UPDATE, // OnUIUpdate (WindowManager Update -> Python OnUpdate)
|
||||
SECTION_UPDATE_GAME, // app.UpdateGame, inside UI_UPDATE
|
||||
SECTION_RENDER_BEGIN, // CCullingManager::Update + the command-list restarts
|
||||
SECTION_UI_RENDER, // OnUIRender (Python OnRender) + OnMouseRender
|
||||
SECTION_RENDER_GAME, // app.RenderGame, inside UI_RENDER
|
||||
SECTION_PYTHON, // outermost C++ -> Python callbacks (includes UPDATE_GAME/RENDER_GAME)
|
||||
SECTION_SHADOW_RASTER, // RecordingDevice's CPU rasterization of the shadow texture, inside RENDER_GAME
|
||||
SECTION_COUNT,
|
||||
};
|
||||
|
||||
struct SCounters
|
||||
{
|
||||
double ms[SECTION_COUNT] = {};
|
||||
unsigned calls[SECTION_COUNT] = {};
|
||||
unsigned pyMissing = 0; // callbacks whose method does not exist
|
||||
int actorCount = 0; // CPythonCharacterManager alive instances, last Process()
|
||||
int scriptCpu = -1; // CPU the script thread last ran on (Linux/Android), else -1
|
||||
};
|
||||
|
||||
inline bool& Enabled()
|
||||
{
|
||||
static bool s_bEnabled = false;
|
||||
return s_bEnabled;
|
||||
}
|
||||
|
||||
inline SCounters& Counters()
|
||||
{
|
||||
static SCounters s_kCounters;
|
||||
return s_kCounters;
|
||||
}
|
||||
|
||||
inline int& PythonDepth()
|
||||
{
|
||||
static int s_iDepth = 0;
|
||||
return s_iDepth;
|
||||
}
|
||||
|
||||
class CScope
|
||||
{
|
||||
public:
|
||||
explicit CScope(ESection eSection) : m_eSection(eSection), m_bActive(Enabled())
|
||||
{
|
||||
if (m_bActive)
|
||||
m_kStart = std::chrono::steady_clock::now();
|
||||
}
|
||||
~CScope()
|
||||
{
|
||||
if (!m_bActive)
|
||||
return;
|
||||
SCounters& rkCounters = Counters();
|
||||
rkCounters.ms[m_eSection] += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_kStart).count();
|
||||
++rkCounters.calls[m_eSection];
|
||||
}
|
||||
|
||||
private:
|
||||
ESection m_eSection;
|
||||
bool m_bActive;
|
||||
std::chrono::steady_clock::time_point m_kStart;
|
||||
};
|
||||
|
||||
// Times only the outermost Python callback, so nested callbacks are not counted twice.
|
||||
class CPythonScope
|
||||
{
|
||||
public:
|
||||
CPythonScope() : m_bActive(Enabled() && PythonDepth()++ == 0)
|
||||
{
|
||||
if (m_bActive)
|
||||
m_kStart = std::chrono::steady_clock::now();
|
||||
}
|
||||
~CPythonScope()
|
||||
{
|
||||
if (!Enabled())
|
||||
return;
|
||||
if (PythonDepth() > 0)
|
||||
--PythonDepth();
|
||||
if (!m_bActive)
|
||||
return;
|
||||
SCounters& rkCounters = Counters();
|
||||
rkCounters.ms[SECTION_PYTHON] += std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_kStart).count();
|
||||
++rkCounters.calls[SECTION_PYTHON];
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_bActive;
|
||||
std::chrono::steady_clock::time_point m_kStart;
|
||||
};
|
||||
|
||||
inline void CountPythonMissing()
|
||||
{
|
||||
if (Enabled())
|
||||
++Counters().pyMissing;
|
||||
}
|
||||
|
||||
// The totals since the last call, which clears them.
|
||||
inline SCounters Take()
|
||||
{
|
||||
SCounters kTaken = Counters();
|
||||
SCounters& rkCounters = Counters();
|
||||
const int iActors = rkCounters.actorCount;
|
||||
const int iCpu = rkCounters.scriptCpu;
|
||||
rkCounters = SCounters();
|
||||
rkCounters.actorCount = iActors;
|
||||
rkCounters.scriptCpu = iCpu;
|
||||
return kTaken;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "RenderCommands3D.h"
|
||||
#include "UIRenderCommands.h"
|
||||
#include "CpuBuffer.h"
|
||||
#include "../EterBase/PerfCounters.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -15,6 +16,7 @@ namespace {
|
||||
std::mutex g_draws_mutex;
|
||||
std::vector<Render3DDraw> g_draws;
|
||||
int g_native_terrain_override = -1;
|
||||
bool g_gpu_render_targets = false;
|
||||
|
||||
|
||||
struct VertexLayout {
|
||||
@@ -434,7 +436,33 @@ public:
|
||||
auto* surface = static_cast<CpuSurface*>(m_renderTarget);
|
||||
if (!surface->parent || surface->level != 0 || surface->desc.Format != D3DFMT_R5G6B5)
|
||||
return S_OK;
|
||||
if (g_gpu_render_targets)
|
||||
{
|
||||
// The whole target, as the CPU path below clears it.
|
||||
Render3DDraw clear;
|
||||
clear.render_target = surface->parent->id;
|
||||
clear.target_width = surface->desc.Width;
|
||||
clear.target_height = surface->desc.Height;
|
||||
clear.clear_flags = flags & (D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER);
|
||||
clear.clear_color = color;
|
||||
clear.clear_z = depth;
|
||||
if (clear.clear_flags)
|
||||
Render3DAdd(std::move(clear));
|
||||
return S_OK;
|
||||
}
|
||||
const size_t pixels = size_t(surface->desc.Width) * surface->desc.Height;
|
||||
if (const char* dump = std::getenv("MT_DUMP_RT"); dump && *dump && surface->parent->levels[0].bytes.size() >= pixels * 2)
|
||||
{
|
||||
// Debug: the previous frame's CPU shadow map, red channel, before it is cleared.
|
||||
if (FILE* out = std::fopen((std::string(dump) + "_cpu.pgm").c_str(), "wb"))
|
||||
{
|
||||
std::fprintf(out, "P5\n%u %u\n255\n", unsigned(surface->desc.Width), unsigned(surface->desc.Height));
|
||||
const auto& bytes = surface->parent->levels[0].bytes;
|
||||
for (size_t i = 0; i < pixels; ++i)
|
||||
std::fputc(int(((bytes[i * 2 + 1] >> 3) & 31) * 255 / 31), out);
|
||||
std::fclose(out);
|
||||
}
|
||||
}
|
||||
if (flags & D3DCLEAR_TARGET)
|
||||
{
|
||||
const std::uint16_t rgb565 = std::uint16_t((((color >> 16) & 255u) >> 3) << 11 |
|
||||
@@ -593,6 +621,7 @@ private:
|
||||
// the normal memory-texture path upload the result for terrain/object projection.
|
||||
void rasterize_shadow(const Render3DDraw& draw)
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_SHADOW_RASTER);
|
||||
auto* surface = static_cast<CpuSurface*>(m_renderTarget);
|
||||
if (!surface->parent || surface->level != 0 || surface->desc.Format != D3DFMT_R5G6B5 || draw.lines)
|
||||
return;
|
||||
@@ -1048,10 +1077,47 @@ private:
|
||||
}
|
||||
if (m_renderTarget == m_backBuffer)
|
||||
Render3DAdd(std::move(draw));
|
||||
else if (g_gpu_render_targets)
|
||||
record_shadow(std::move(draw));
|
||||
else
|
||||
rasterize_shadow(draw);
|
||||
}
|
||||
|
||||
// GPU counterpart of rasterize_shadow: the draw goes to the renderer's offscreen pass with the
|
||||
// states rasterize_shadow implements -- a flat D3DRS_TEXTUREFACTOR fill, no culling, no texture,
|
||||
// light, fog or blending, depth test LESSEQUAL with depth writes -- so both paths draw the same map.
|
||||
void record_shadow(Render3DDraw draw)
|
||||
{
|
||||
auto* surface = static_cast<CpuSurface*>(m_renderTarget);
|
||||
if (!surface->parent || surface->level != 0 || surface->desc.Format != D3DFMT_R5G6B5 || draw.lines ||
|
||||
draw.pretransformed || draw.indices.size() < 3)
|
||||
return;
|
||||
draw.render_target = surface->parent->id;
|
||||
draw.target_width = surface->desc.Width;
|
||||
draw.target_height = surface->desc.Height;
|
||||
draw.ui_offset_x = 0;
|
||||
draw.texture0.clear();
|
||||
draw.texture1.clear();
|
||||
draw.texture_factor = m_renderStates[D3DRS_TEXTUREFACTOR];
|
||||
draw.color_op[0] = D3DTOP_SELECTARG1;
|
||||
draw.color_arg1[0] = D3DTA_TFACTOR;
|
||||
draw.alpha_op[0] = D3DTOP_SELECTARG1;
|
||||
draw.alpha_arg1[0] = D3DTA_TFACTOR;
|
||||
draw.color_op[1] = D3DTOP_DISABLE;
|
||||
draw.alpha_op[1] = D3DTOP_DISABLE;
|
||||
draw.lighting = FALSE;
|
||||
draw.light0 = false;
|
||||
for (auto& light : draw.lights) light.type = 0;
|
||||
draw.fog_enable = FALSE;
|
||||
draw.alpha_blend = FALSE;
|
||||
draw.alpha_test = FALSE;
|
||||
draw.cull_mode = D3DCULL_NONE;
|
||||
draw.z_enable = TRUE;
|
||||
draw.z_write = TRUE;
|
||||
draw.z_func = D3DCMP_LESSEQUAL;
|
||||
Render3DAdd(std::move(draw));
|
||||
}
|
||||
|
||||
ULONG m_refs = 1;
|
||||
int m_width, m_height;
|
||||
D3DVIEWPORT8 m_viewport = {};
|
||||
@@ -1089,6 +1155,11 @@ std::string MtCpuTextureNameFromHandle(const IDirect3DBaseTexture8* handle)
|
||||
std::lock_guard<std::mutex> lock(g_cpu_tex_mutex);
|
||||
for (const auto& entry : g_cpu_textures)
|
||||
{
|
||||
if (entry.second != handle || entry.second->levels.empty())
|
||||
continue;
|
||||
const D3DSURFACE_DESC& desc = entry.second->levels[0].desc;
|
||||
if (g_gpu_render_targets && (desc.Usage & D3DUSAGE_RENDERTARGET) && desc.Format == D3DFMT_R5G6B5)
|
||||
return Render3DRenderTargetName(entry.first, desc.Width, desc.Height);
|
||||
if (entry.second == handle && !entry.second->levels.empty())
|
||||
return "mem:cpu_" + std::to_string(entry.first) + "@" + std::to_string(entry.second->revision);
|
||||
}
|
||||
@@ -1168,6 +1239,14 @@ bool IsNativeTerrainRenderEnabled()
|
||||
return env_on;
|
||||
}
|
||||
|
||||
void SetGpuRenderTargetsEnabled(bool enabled) { g_gpu_render_targets = enabled; }
|
||||
bool IsGpuRenderTargetsEnabled() { return g_gpu_render_targets; }
|
||||
|
||||
std::string Render3DRenderTargetName(std::uint32_t id, std::uint32_t width, std::uint32_t height)
|
||||
{
|
||||
return "rt:" + std::to_string(id) + ":" + std::to_string(width) + "x" + std::to_string(height);
|
||||
}
|
||||
|
||||
void Render3DBeginFrame()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_draws_mutex);
|
||||
|
||||
@@ -29,6 +29,13 @@ struct Render3DDraw {
|
||||
std::uint32_t clear_color = 0; // 0xAARRGGBB
|
||||
float clear_z = 1;
|
||||
|
||||
// Non-zero: the draw (or clear) targets an offscreen render-target texture instead of the back
|
||||
// buffer -- the 40250 character shadow map. The renderer draws these into the texture named
|
||||
// Render3DRenderTargetName(render_target, ...) ahead of the back-buffer pass, in recorded order.
|
||||
// viewport is then in the target's pixels. Only emitted while GPU render targets are enabled.
|
||||
std::uint32_t render_target = 0;
|
||||
std::uint32_t target_width = 0, target_height = 0;
|
||||
|
||||
// Stage 0/1 textures, named like UIRenderTextureName: the pack path of a file texture or
|
||||
// "mem:<id>@<revision>"; empty when the stage has no texture.
|
||||
std::string texture0;
|
||||
@@ -122,6 +129,13 @@ bool LookupGpuSkinSubrange(const void* vertex_buffer_base, std::uint32_t stride,
|
||||
std::uint32_t lo_vertex, std::uint32_t hi_vertex,
|
||||
GpuSkinSubrangeView* out_view);
|
||||
|
||||
// GPU render targets: shadow-map draws are recorded with render_target set and sampled as
|
||||
// "rt:<id>:<w>x<h>" instead of being rasterized on the CPU into a "mem:cpu_<id>" texture. Off by
|
||||
// default (the Godot consumer has no offscreen pass); the native Vulkan renderer turns it on.
|
||||
void SetGpuRenderTargetsEnabled(bool enabled);
|
||||
bool IsGpuRenderTargetsEnabled();
|
||||
std::string Render3DRenderTargetName(std::uint32_t id, std::uint32_t width, std::uint32_t height);
|
||||
|
||||
void Render3DBeginFrame();
|
||||
void Render3DAdd(Render3DDraw draw);
|
||||
const std::vector<Render3DDraw>& Render3DDraws();
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "../EterBase/LogBox.h"
|
||||
#include "../UserInterface/ServerClock.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
@@ -61,6 +62,9 @@
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -185,6 +189,7 @@ struct ScriptFiber
|
||||
};
|
||||
ScriptFiber g_fiber;
|
||||
thread_local bool t_on_fiber = false;
|
||||
std::atomic<int> g_script_thread_id{0};
|
||||
|
||||
// Caller holds the GIL. Returns with the GIL held again after the script has given the baton back.
|
||||
void hand_to_script()
|
||||
@@ -218,6 +223,9 @@ void* fiber_main(void*)
|
||||
g_fiber.cv.wait(lock, [] { return g_fiber.turn == ScriptFiber::Script; });
|
||||
}
|
||||
t_on_fiber = true;
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
g_script_thread_id = int(gettid());
|
||||
#endif
|
||||
PyGILState_STATE gil = PyGILState_Ensure();
|
||||
std::string error;
|
||||
const bool ok = run_main_script_body(g_fiber.command_line.c_str(), &error);
|
||||
@@ -927,6 +935,11 @@ bool RunMainScript(const char* lpCmdLine, std::string* error)
|
||||
return true;
|
||||
}
|
||||
|
||||
int ScriptThreadId()
|
||||
{
|
||||
return g_script_thread_id;
|
||||
}
|
||||
|
||||
bool IsAppLooping()
|
||||
{
|
||||
return g_fiber.alive && g_fiber.looping && !g_fiber.finished;
|
||||
|
||||
@@ -40,6 +40,9 @@ bool CursorVisible();
|
||||
// CPythonApplication::Process() while the host thread waits, so the two never run concurrently.
|
||||
bool RunMainScript(const char* lpCmdLine, std::string* error);
|
||||
bool IsAppLooping();
|
||||
// The script thread's kernel thread id (Linux/Android), 0 before it starts or elsewhere. The host adds
|
||||
// it to its ADPF performance-hint session.
|
||||
int ScriptThreadId();
|
||||
bool AppFrame();
|
||||
// Native CPythonBackground map selection, in the local coordinate frame of RenderGame.
|
||||
std::string CurrentMapName();
|
||||
|
||||
@@ -22,6 +22,11 @@
|
||||
#include "UserInterface/PythonSystem.h"
|
||||
#include "../ScriptLib/PythonBoot.h"
|
||||
#include "ServerClock.h"
|
||||
#include "../EterBase/PerfCounters.h"
|
||||
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
#include <sched.h>
|
||||
#endif
|
||||
|
||||
// 2V0-d link surface; 2V0-f adds the application lifecycle (Create/Loop/Process/Exit) that
|
||||
// prototype.RunApp() drives. The rest of the members still wait for the game slices (2V1-2V3).
|
||||
@@ -196,6 +201,7 @@ void CPythonApplication::GetMousePosition(POINT* ppt)
|
||||
// PORT: the members are reached through their singletons (see the note at the top of the file).
|
||||
void CPythonApplication::RenderGame()
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_RENDER_GAME);
|
||||
float fAspect=UI::CWindowManager::Instance().GetAspect();
|
||||
float fFarClip=CPythonBackground::Instance().GetFarClip();
|
||||
|
||||
@@ -253,6 +259,7 @@ void CPythonApplication::RenderGame()
|
||||
// calls it every frame. PORT: members through their singletons, as in RenderGame.
|
||||
void CPythonApplication::UpdateGame()
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_UPDATE_GAME);
|
||||
POINT ptMouse;
|
||||
GetMousePosition(&ptMouse);
|
||||
|
||||
@@ -293,19 +300,42 @@ void CPythonApplication::UpdateGame()
|
||||
// input instead), camera (2V3), resource GC, mouse/UI update, then the render block's interface pass.
|
||||
// PORT: the frame-skip bookkeeping and the game render pass are left out; the Godot frame clock
|
||||
// decides when this runs, and RenderGame belongs to 2V3.
|
||||
// PORT: telemetry for --perf-log (platform/EterBase/PerfCounters.h); 40250 has no counterpart.
|
||||
static void __PerfSampleWorld()
|
||||
{
|
||||
if (!MtPerf::Enabled())
|
||||
return;
|
||||
MtPerf::SCounters& rkCounters = MtPerf::Counters();
|
||||
CPythonCharacterManager& rkChrMgr = CPythonCharacterManager::Instance();
|
||||
int iActors = 0;
|
||||
for (CPythonCharacterManager::CharacterIterator i = rkChrMgr.CharacterInstanceBegin(); i != rkChrMgr.CharacterInstanceEnd(); ++i)
|
||||
++iActors;
|
||||
rkCounters.actorCount = iActors;
|
||||
#if defined(__linux__) || defined(__ANDROID__)
|
||||
rkCounters.scriptCpu = sched_getcpu();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool CPythonApplication::Process()
|
||||
{
|
||||
MtPerf::CScope kPerfProcess(MtPerf::SECTION_PROCESS);
|
||||
__PerfSampleWorld();
|
||||
ELTimer_SetFrameMSec();
|
||||
|
||||
CTimer& rkTimer=CTimer::Instance();
|
||||
rkTimer.Advance();
|
||||
|
||||
// Network I/O
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_NETWORK);
|
||||
CPythonNetworkStream::Instance().Process();
|
||||
// PORT: m_kGuildMarkUploader/m_kGuildMarkDownloader.Process() follow here in 40250; the guild mark
|
||||
// transfer is not ported yet (GuildMarkDownloader.cpp/GuildMarkUploader.cpp).
|
||||
CAccountConnector::Instance().Process();
|
||||
}
|
||||
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_CAMERA);
|
||||
//!@# Alt+Tab 중 SetTransfor 에서 튕김 현상 해결을 위해 - [levites]
|
||||
//if (m_isActivateWnd)
|
||||
__UpdateCamera();
|
||||
@@ -315,7 +345,11 @@ bool CPythonApplication::Process()
|
||||
OnCameraUpdate();
|
||||
if (IsNativeTerrainRenderEnabled())
|
||||
OnMouseUpdate();
|
||||
}
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_UI_UPDATE);
|
||||
OnUIUpdate();
|
||||
}
|
||||
|
||||
if (IsNativeTerrainRenderEnabled())
|
||||
CGrannyMaterial::TranslateSpecularMatrix(g_specularSpd, g_specularSpd, 0.0f);
|
||||
@@ -325,14 +359,19 @@ bool CPythonApplication::Process()
|
||||
// Win32 activation (CMSWindow::IsActive is a stub). The frame's UI and 3D command lists restart here.
|
||||
// 40250 PythonApplication.cpp:681.
|
||||
DWORD dwRenderStartTime = ELTimer_GetMSec();
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_RENDER_BEGIN);
|
||||
CCullingManager::Instance().Update();
|
||||
UIRenderBeginFrame();
|
||||
Render3DBeginFrame();
|
||||
}
|
||||
CPythonGraphic& rkGraphic = CPythonGraphic::Instance();
|
||||
if (rkGraphic.Begin())
|
||||
{
|
||||
rkGraphic.SetInterfaceRenderState();
|
||||
|
||||
{
|
||||
MtPerf::CScope kPerf(MtPerf::SECTION_UI_RENDER);
|
||||
OnUIRender();
|
||||
if (IsNativeTerrainRenderEnabled())
|
||||
{
|
||||
@@ -342,6 +381,7 @@ bool CPythonApplication::Process()
|
||||
UIRenderSetClip(0.0f, 0.0f, float(cw), float(ch));
|
||||
OnMouseRender();
|
||||
}
|
||||
}
|
||||
|
||||
rkGraphic.End();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "StdAfx.h"
|
||||
#include "PythonUtils.h"
|
||||
#include "../../platform/EterBase/PerfCounters.h" // PORT: --perf-log callback timing
|
||||
|
||||
IPythonExceptionSender * g_pkExceptionSender = NULL;
|
||||
|
||||
@@ -291,6 +292,7 @@ bool PyCallClassMemberFunc(PyObject* poClass, const char* c_szFunc, PyObject* po
|
||||
*/
|
||||
bool __PyCallClassMemberFunc_ByCString(PyObject* poClass, const char* c_szFunc, PyObject* poArgs, PyObject** ppoRet)
|
||||
{
|
||||
MtPerf::CPythonScope kPerf;
|
||||
if (!poClass)
|
||||
{
|
||||
Py_XDECREF(poArgs);
|
||||
@@ -302,6 +304,7 @@ bool __PyCallClassMemberFunc_ByCString(PyObject* poClass, const char* c_szFunc,
|
||||
if (!poFunc)
|
||||
{
|
||||
PyErr_Clear();
|
||||
MtPerf::CountPythonMissing();
|
||||
Py_XDECREF(poArgs);
|
||||
return false;
|
||||
}
|
||||
@@ -339,6 +342,7 @@ bool __PyCallClassMemberFunc_ByCString(PyObject* poClass, const char* c_szFunc,
|
||||
|
||||
bool __PyCallClassMemberFunc_ByPyString(PyObject* poClass, PyObject* poFuncName, PyObject* poArgs, PyObject** ppoRet)
|
||||
{
|
||||
MtPerf::CPythonScope kPerf;
|
||||
if (!poClass)
|
||||
{
|
||||
Py_XDECREF(poArgs);
|
||||
@@ -350,6 +354,7 @@ bool __PyCallClassMemberFunc_ByPyString(PyObject* poClass, PyObject* poFuncName,
|
||||
if (!poFunc)
|
||||
{
|
||||
PyErr_Clear();
|
||||
MtPerf::CountPythonMissing();
|
||||
Py_XDECREF(poArgs);
|
||||
return false;
|
||||
}
|
||||
@@ -387,6 +392,7 @@ bool __PyCallClassMemberFunc_ByPyString(PyObject* poClass, PyObject* poFuncName,
|
||||
|
||||
bool __PyCallClassMemberFunc(PyObject* poClass, PyObject * poFunc, PyObject* poArgs, PyObject** ppoRet)
|
||||
{
|
||||
MtPerf::CPythonScope kPerf;
|
||||
if (!poClass)
|
||||
{
|
||||
Py_XDECREF(poArgs);
|
||||
@@ -396,6 +402,7 @@ bool __PyCallClassMemberFunc(PyObject* poClass, PyObject * poFunc, PyObject* poA
|
||||
if (!poFunc)
|
||||
{
|
||||
PyErr_Clear();
|
||||
MtPerf::CountPythonMissing();
|
||||
Py_XDECREF(poArgs);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,17 @@ namespace
|
||||
{
|
||||
constexpr std::uint32_t kHandshake = 0x2468ace0;
|
||||
constexpr int kHandshakeRetryLimit = 32; // game/src/desc.h HANDSHAKE_RETRY_LIMIT
|
||||
constexpr int kIdleMs = 20000;
|
||||
// A client that sends nothing for this long is dropped. MT_FAKE_IDLE_MS raises it for runs that stand
|
||||
// still in game (perf measurements).
|
||||
int idle_ms()
|
||||
{
|
||||
static const int ms = [] {
|
||||
const char* value = std::getenv("MT_FAKE_IDLE_MS");
|
||||
const long parsed = value && *value ? std::strtol(value, nullptr, 10) : 0;
|
||||
return parsed >= 1000 ? static_cast<int>(parsed) : 20000;
|
||||
}();
|
||||
return ms;
|
||||
}
|
||||
|
||||
int fake_mob_count()
|
||||
{
|
||||
@@ -127,7 +137,7 @@ struct FakeLoginServer::Connection
|
||||
int ready = poll(&pfd, 1, 50);
|
||||
if (ready == 0)
|
||||
{
|
||||
if ((waited += 50) >= kIdleMs)
|
||||
if ((waited += 50) >= idle_ms())
|
||||
return -1;
|
||||
continue;
|
||||
}
|
||||
@@ -222,7 +232,7 @@ bool FakeLoginServer::Accept(int listener, Connection& c)
|
||||
c.fd = accept(listener, nullptr, nullptr);
|
||||
return c.fd >= 0;
|
||||
}
|
||||
if (waited >= kIdleMs)
|
||||
if (waited >= idle_ms())
|
||||
return Fail("no client connected");
|
||||
}
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user