Performance optimizations: sound caching, renderer culling, entity and lighting improvements

- Sound engine: cache filesystem probe results to avoid repeated file existence checks; add MA_SOUND_FLAG_DECODE for pre-decoded playback
- Level renderer: column-level frustum culling, compact visible chunk lists to skip empty iteration, lightweight second-pass render path, early-out for non-dirty chunks, scaled recheck period at high render distances
- Entity: cache shared_from_this() to reduce reference counting overhead in move() and checkInsideTiles()
- Level: skip checkLight() when tile light properties unchanged; enable entity locking on all platforms (not just Vita)
- LevelChunk: only rescan min height when the minimum column changes; defer lightGap processing
- LivingEntity: use raw pointer cast instead of dynamic_pointer_cast; cache friction tile lookup
- ServerPlayerGameMode: return whether block was destroyed to avoid redundant tile update packets
This commit is contained in:
Revela
2026-03-18 17:22:04 -05:00
parent 80ba2e0762
commit 0ba923f085
12 changed files with 311 additions and 170 deletions
+79 -59
View File
@@ -468,65 +468,69 @@ void SoundEngine::play(int iSound, float x, float y, float z, float volume, floa
sprintf_s(basePath, "Windows64Media/Sound/%s", (char*)szSoundName);
char finalPath[256];
sprintf_s(finalPath, "%s.wav", basePath);
const char* extensions[] = { ".ogg", ".wav", ".mp3" };
size_t extCount = sizeof(extensions) / sizeof(extensions[0]);
bool found = false;
for (size_t extIdx = 0; extIdx < extCount; extIdx++)
// Check path cache first to avoid expensive filesystem probing
auto cacheIt = m_soundPathCache.find(iSound);
if (cacheIt != m_soundPathCache.end())
{
char basePlusExt[256];
sprintf_s(basePlusExt, "%s%s", basePath, extensions[extIdx]);
DWORD attr = GetFileAttributesA(basePlusExt);
if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY))
{
sprintf_s(finalPath, "%s", basePlusExt);
found = true;
break;
}
const auto& paths = cacheIt->second;
if (paths.empty())
return; // previously probed, no files found
const std::string& chosen = paths[rand() % paths.size()];
sprintf_s(finalPath, "%s", chosen.c_str());
}
if (!found)
else
{
int count = 0;
// Cache miss — probe filesystem and store results
std::vector<std::string> validPaths;
const char* extensions[] = { ".ogg", ".wav", ".mp3" };
size_t extCount = sizeof(extensions) / sizeof(extensions[0]);
bool found = false;
// Check base name with each extension (non-numbered)
for (size_t extIdx = 0; extIdx < extCount; extIdx++)
{
for (size_t i = 1; i < 32; i++)
char basePlusExt[256];
sprintf_s(basePlusExt, "%s%s", basePath, extensions[extIdx]);
DWORD attr = GetFileAttributesA(basePlusExt);
if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY))
{
char numberedPath[256];
sprintf_s(numberedPath, "%s%d%s", basePath, i, extensions[extIdx]);
DWORD attr = GetFileAttributesA(numberedPath);
if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY))
validPaths.push_back(basePlusExt);
found = true;
break; // non-numbered: only one base file needed
}
}
if (!found)
{
// Check numbered variants (e.g. sound1.ogg, sound2.ogg, ...)
for (size_t extIdx = 0; extIdx < extCount; extIdx++)
{
for (int i = 1; i < 32; i++)
{
count = i;
char numberedPath[256];
sprintf_s(numberedPath, "%s%d%s", basePath, i, extensions[extIdx]);
DWORD attr = GetFileAttributesA(numberedPath);
if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY))
{
validPaths.push_back(numberedPath);
}
}
}
}
if (count > 0)
m_soundPathCache[iSound] = validPaths;
if (validPaths.empty())
{
int chosen = (rand() % count) + 1;
for (size_t extIdx = 0; extIdx < extCount; extIdx++)
{
char numberedPath[256];
sprintf_s(numberedPath, "%s%d%s", basePath, chosen, extensions[extIdx]);
DWORD attr = GetFileAttributesA(numberedPath);
if (attr != INVALID_FILE_ATTRIBUTES && !(attr & FILE_ATTRIBUTE_DIRECTORY))
{
sprintf_s(finalPath, "%s", numberedPath);
found = true;
break;
}
}
if (!found)
{
sprintf_s(finalPath, "%s%d.wav", basePath, chosen);
}
sprintf_s(finalPath, "%s.wav", basePath); // fallback for debug print
}
else
{
const std::string& chosen = validPaths[rand() % validPaths.size()];
sprintf_s(finalPath, "%s", chosen.c_str());
}
}
@@ -546,7 +550,7 @@ void SoundEngine::play(int iSound, float x, float y, float z, float volume, floa
if (ma_sound_init_from_file(
&m_engine,
finalPath,
MA_SOUND_FLAG_ASYNC,
MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC,
nullptr,
nullptr,
&s->sound) != MA_SUCCESS)
@@ -602,24 +606,40 @@ void SoundEngine::playUI(int iSound, float volume, float pitch)
sprintf_s(basePath, "Windows64Media/Sound/Minecraft/UI/%s", ConvertSoundPathToName(name));
char finalPath[256];
sprintf_s(finalPath, "%s.wav", basePath);
const char* extensions[] = { ".ogg", ".wav", ".mp3" };
size_t count = sizeof(extensions) / sizeof(extensions[0]);
bool found = false;
for (size_t i = 0; i < count; i++)
// Check UI sound path cache first
auto cacheIt = m_uiSoundPathCache.find(iSound);
if (cacheIt != m_uiSoundPathCache.end())
{
sprintf_s(finalPath, "%s%s", basePath, extensions[i]);
if (FileExists(finalPath))
if (cacheIt->second.empty())
{
found = true;
break;
app.DebugPrintf("No sound file found for UI sound (cached): %s\n", basePath);
return;
}
sprintf_s(finalPath, "%s", cacheIt->second.c_str());
}
if (!found)
else
{
app.DebugPrintf("No sound file found for UI sound: %s\n", basePath);
return;
// Cache miss — probe filesystem and store result
const char* extensions[] = { ".ogg", ".wav", ".mp3" };
size_t count = sizeof(extensions) / sizeof(extensions[0]);
bool found = false;
for (size_t i = 0; i < count; i++)
{
sprintf_s(finalPath, "%s%s", basePath, extensions[i]);
if (FileExists(finalPath))
{
found = true;
break;
}
}
if (!found)
{
m_uiSoundPathCache[iSound] = ""; // cache negative result
app.DebugPrintf("No sound file found for UI sound: %s\n", basePath);
return;
}
m_uiSoundPathCache[iSound] = finalPath;
}
MiniAudioSound* s = new MiniAudioSound();
@@ -633,7 +653,7 @@ void SoundEngine::playUI(int iSound, float volume, float pitch)
if (ma_sound_init_from_file(
&m_engine,
finalPath,
MA_SOUND_FLAG_ASYNC,
MA_SOUND_FLAG_DECODE | MA_SOUND_FLAG_ASYNC,
nullptr,
nullptr,
&s->sound) != MA_SUCCESS)
@@ -5,6 +5,8 @@ using namespace std;
#include "..\..\Minecraft.World\SoundTypes.h"
#include "miniaudio.h"
#include <unordered_map>
#include <string>
constexpr float SFX_3D_MIN_DISTANCE = 1.0f;
constexpr float SFX_3D_MAX_DISTANCE = 16.0f;
@@ -187,6 +189,9 @@ private:
int m_iStream_CD_1;
bool *m_bHeardTrackA;
std::unordered_map<int, std::vector<std::string>> m_soundPathCache; // play(): sound ID → all valid variant paths
std::unordered_map<int, std::string> m_uiSoundPathCache; // playUI(): sound ID → resolved path
#ifdef __ORBIS__
int32_t m_hBGMAudio;
#endif
+1 -1
View File
@@ -1545,7 +1545,7 @@ void GameRenderer::renderLevel(float a, int64_t until)
if (visibleWaterChunks > 0)
{
PIXBeginNamedEvent(0,"Fancy second pass - actual rendering");
levelRenderer->render(cameraEntity, 1, a, updateChunks); // 4J - chanaged, used to be renderSameAsLast but we don't support that anymore
levelRenderer->renderChunksDirect(1, a); // Lightweight path — skips redundant allChanged/resortChunks checks
PIXEndNamedEvent();
}
+174 -58
View File
@@ -157,6 +157,11 @@ LevelRenderer::LevelRenderer(Minecraft *mc, Textures *textures)
dirtyChunkPresent = false;
lastDirtyChunkFound = 0;
visibleLists_layer0 = nullptr;
visibleLists_layer1 = nullptr;
visibleCount_layer0 = 0;
visibleCount_layer1 = 0;
this->mc = mc;
this->textures = textures;
@@ -455,6 +460,12 @@ void LevelRenderer::allChanged(int playerIndex)
// delete sortedChunks[playerIndex]; // 4J - removed - not sorting our chunks anymore
}
// Free old visible chunk lists
delete[] visibleLists_layer0;
delete[] visibleLists_layer1;
visibleLists_layer0 = nullptr;
visibleLists_layer1 = nullptr;
chunks[playerIndex] = ClipChunkArray(xChunks * yChunks * zChunks);
// sortedChunks[playerIndex] = new vector<Chunk *>(xChunks * yChunks * zChunks); // 4J - removed - not sorting our chunks anymore
int id = 0;
@@ -487,6 +498,13 @@ void LevelRenderer::allChanged(int playerIndex)
}
nonStackDirtyChunksAdded();
// Allocate visible chunk lists (worst case: all chunks visible)
int totalChunkCount = xChunks * yChunks * zChunks;
visibleLists_layer0 = new int[totalChunkCount];
visibleLists_layer1 = new int[totalChunkCount];
visibleCount_layer0 = 0;
visibleCount_layer1 = 0;
if (level != nullptr)
{
shared_ptr<Entity> player = mc->cameraTargetPlayer;
@@ -705,42 +723,42 @@ int LevelRenderer::render(shared_ptr<LivingEntity> player, int layer, double alp
{
int playerIndex = mc->player->GetXboxPad();
// 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require
if( lastPlayerCount[playerIndex] != activePlayers() )
{
allChanged();
}
else if (mc->options->viewDistance != lastViewDistance)
{
allChanged();
}
// Only check allChanged/resortChunks on layer 0 — they only need to run once per frame
if (layer == 0)
{
// 4J - added - if the number of players has changed, we need to rebuild things for the new draw distance this will require
if( lastPlayerCount[playerIndex] != activePlayers() )
{
allChanged();
}
else if (mc->options->viewDistance != lastViewDistance)
{
allChanged();
}
totalChunks = 0;
offscreenChunks = 0;
occludedChunks = 0;
renderedChunks = 0;
emptyChunks = 0;
double xd = player->x - xOld[playerIndex];
double yd = player->y - yOld[playerIndex];
double zd = player->z - zOld[playerIndex];
if (xd * xd + yd * yd + zd * zd > 4 * 4)
{
xOld[playerIndex] = player->x;
yOld[playerIndex] = player->y;
zOld[playerIndex] = player->z;
resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
}
}
double xOff = player->xOld + (player->x - player->xOld) * alpha;
double yOff = player->yOld + (player->y - player->yOld) * alpha;
double zOff = player->zOld + (player->z - player->zOld) * alpha;
double xd = player->x - xOld[playerIndex];
double yd = player->y - yOld[playerIndex];
double zd = player->z - zOld[playerIndex];
if (xd * xd + yd * yd + zd * zd > 4 * 4)
{
xOld[playerIndex] = player->x;
yOld[playerIndex] = player->y;
zOld[playerIndex] = player->z;
resortChunks(Mth::floor(player->x), Mth::floor(player->y), Mth::floor(player->z));
// sort(sortedChunks[playerIndex]->begin(),sortedChunks[playerIndex]->end(), DistanceChunkSorter(player)); // 4J - removed - not sorting our chunks anymore
}
Lighting::turnOff();
int count = renderChunks(0, static_cast<int>(chunks[playerIndex].length), layer, alpha);
@@ -749,6 +767,38 @@ int LevelRenderer::render(shared_ptr<LivingEntity> player, int layer, double alp
}
// Lightweight render path for the second layer 1 pass — skips allChanged/resortChunks checks
// and just does GL setup + visible list iteration + cleanup.
// Assumes Lighting::turnOff() was already called by the prior render() invocation.
void LevelRenderer::renderChunksDirect(int layer, double alpha)
{
shared_ptr<LivingEntity> player = mc->cameraTargetPlayer;
if (player == nullptr) return;
mc->gameRenderer->turnOnLightLayer(alpha);
double xOff = player->xOld + (player->x - player->xOld) * alpha;
double yOff = player->yOld + (player->y - player->yOld) * alpha;
double zOff = player->zOld + (player->z - player->zOld) * alpha;
glPushMatrix();
glTranslatef(static_cast<float>(-xOff), static_cast<float>(-yOff), static_cast<float>(-zOff));
int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1;
int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1;
bool first = true;
if (lists != nullptr)
{
for (int i = 0; i < numVisible; i++)
{
if (RenderManager.CBuffCall(lists[i], first))
first = false;
}
}
glPopMatrix();
mc->gameRenderer->turnOffLightLayer(alpha);
}
#ifdef __PSVITA__
#include <stdlib.h>
@@ -819,23 +869,35 @@ int LevelRenderer::renderChunks(int from, int to, int layer, double alpha)
bool first = true;
int count = 0;
ClipChunk *pClipChunk = chunks[playerIndex].data;
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ )
// Use compact visible lists built during cull() instead of iterating all chunks
int *lists = (layer == 0) ? visibleLists_layer0 : visibleLists_layer1;
int numVisible = (layer == 0) ? visibleCount_layer0 : visibleCount_layer1;
if (lists != nullptr)
{
if( !pClipChunk->visible ) continue; // This will be set if the chunk isn't visible, or isn't compiled, or has both empty flags set
if( pClipChunk->globalIdx == -1 ) continue; // Not sure if we should ever encounter this... TODO check
if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue; // Check that this particular layer isn't empty
// List can be calculated directly from the chunk's global idex
int list = pClipChunk->globalIdx * 2 + layer;
list += chunkLists;
if(RenderManager.CBuffCall(list, first))
for (int i = 0; i < numVisible; i++)
{
first = false;
if (RenderManager.CBuffCall(lists[i], first))
first = false;
count++;
}
}
else
{
// Fallback: iterate all chunks (before visible lists are allocated)
ClipChunk *pClipChunk = chunks[playerIndex].data;
unsigned char emptyFlag = LevelRenderer::CHUNK_FLAG_EMPTY0 << layer;
for( int i = 0; i < chunks[playerIndex].length; i++, pClipChunk++ )
{
if( !pClipChunk->visible ) continue;
if( pClipChunk->globalIdx == -1 ) continue;
if( ( globalChunkFlags[pClipChunk->globalIdx] & emptyFlag ) == emptyFlag ) continue;
int list = pClipChunk->globalIdx * 2 + layer;
list += chunkLists;
if(RenderManager.CBuffCall(list, first))
first = false;
count++;
}
count++;
}
#ifdef __PSVITA__
@@ -1968,6 +2030,9 @@ bool LevelRenderer::updateDirtyChunks()
for( int y = 0; y < CHUNK_Y_COUNT; y++ )
{
ClipChunk *pClipChunk = &chunks[p][(z * yChunks + y) * xChunks + x];
// Early-out for non-dirty chunks - avoids distance calculation for the vast majority at steady state
if( !(globalChunkFlags[ pClipChunk->globalIdx ] & CHUNK_FLAG_DIRTY) )
continue;
// Get distance to this chunk - deliberately not calling the chunk's method of doing this to avoid overheads (passing entitie, type conversion etc.) that this involves
int xd = pClipChunk->xm - px;
int yd = pClipChunk->ym - py;
@@ -2163,7 +2228,9 @@ bool LevelRenderer::updateDirtyChunks()
else
{
// Nothing to do - clear flags that there are things to process, unless it's been a while since we found any dirty chunks in which case force a check next time through
if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS )
// Scale recheck period with render distance to reduce wasted full-scans at high distances
int recheckPeriod = (xChunks >= 60) ? 1000 : (xChunks >= 40) ? 500 : FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS;
if( ( System::currentTimeMillis() - lastDirtyChunkFound ) > recheckPeriod )
{
dirtyChunkPresent = true;
}
@@ -2564,32 +2631,81 @@ void LevelRenderer::cull(Culler *culler, float a)
fdraw[i * 4 + 3] = static_cast<float>(fd->m_Frustum[i][3] + (fx * -fc->xOff) + (fy * -fc->yOff) + (fz * -fc->zOff));
}
ClipChunk *pClipChunk = chunks[playerIndex].data;
int vis = 0;
int total = 0;
int numWrong = 0;
for (unsigned int i = 0; i < chunks[playerIndex].length; i++)
// Reset visible chunk lists for this frame
visibleCount_layer0 = 0;
visibleCount_layer1 = 0;
// Column-level frustum culling: test one AABB per XZ column before testing individual Y chunks.
// At dist 64 this reduces ~278K clip() calls to ~17K column tests + per-chunk tests only for visible columns.
for (int x = 0; x < xChunks; x++)
{
unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ];
for (int z = 0; z < zChunks; z++)
{
// Build column AABB from bottom and top chunks in this column
ClipChunk *bottomChunk = &chunks[playerIndex][(z * yChunks + 0) * xChunks + x];
ClipChunk *topChunk = &chunks[playerIndex][(z * yChunks + (yChunks - 1)) * xChunks + x];
float columnAABB[6] = {
bottomChunk->aabb[0], bottomChunk->aabb[1], bottomChunk->aabb[2], // minX, minY, minZ
bottomChunk->aabb[3], topChunk->aabb[4], bottomChunk->aabb[5] // maxX, maxY(top), maxZ
};
// Always perform frustum cull test
bool clipres = clip(pClipChunk->aabb, fdraw);
// Test entire column against frustum
if (!clip(columnAABB, fdraw))
{
// Entire column outside frustum — mark all Y chunks invisible
for (int y = 0; y < yChunks; y++)
{
ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x];
pClipChunk->visible = false;
}
continue;
}
if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) )
{
pClipChunk->visible = clipres;
if( pClipChunk->visible ) vis++;
total++;
// Column is (partially) in frustum — test individual chunks
for (int y = 0; y < yChunks; y++)
{
ClipChunk *pClipChunk = &chunks[playerIndex][(z * yChunks + y) * xChunks + x];
unsigned char flags = pClipChunk->globalIdx == -1 ? 0 : globalChunkFlags[ pClipChunk->globalIdx ];
// Skip frustum test for confirmed-empty compiled chunks - they have nothing to render
if ((flags & CHUNK_FLAG_COMPILED) && (flags & CHUNK_FLAG_EMPTYBOTH) == CHUNK_FLAG_EMPTYBOTH)
{
pClipChunk->visible = false;
continue;
}
bool clipres = clip(pClipChunk->aabb, fdraw);
if ( (flags & CHUNK_FLAG_COMPILED ) && ( ( flags & CHUNK_FLAG_EMPTYBOTH ) != CHUNK_FLAG_EMPTYBOTH ) )
{
pClipChunk->visible = clipres;
if( pClipChunk->visible ) vis++;
total++;
}
else if (clipres)
{
pClipChunk->visible = true;
}
else
{
pClipChunk->visible = false;
}
// Build compact visible chunk lists for renderChunks()
if (pClipChunk->visible && pClipChunk->globalIdx != -1 && visibleLists_layer0 != nullptr)
{
int list = pClipChunk->globalIdx * 2 + chunkLists;
if (!((flags & CHUNK_FLAG_EMPTY0) == CHUNK_FLAG_EMPTY0))
visibleLists_layer0[visibleCount_layer0++] = list;
if (!((flags & CHUNK_FLAG_EMPTY1) == CHUNK_FLAG_EMPTY1))
visibleLists_layer1[visibleCount_layer1++] = list + 1;
}
}
}
else if (clipres)
{
pClipChunk->visible = true;
}
else
{
pClipChunk->visible = false;
}
pClipChunk++;
}
}
+7
View File
@@ -83,6 +83,7 @@ private:
void resortChunks(int xc, int yc, int zc);
public:
int render(shared_ptr<LivingEntity> player, int layer, double alpha, bool updateChunks);
void renderChunksDirect(int layer, double alpha);
private:
int renderChunks(int from, int to, int layer, double alpha);
public:
@@ -270,6 +271,12 @@ public:
XLockFreeStack<int> dirtyChunksLockFreeStack;
// Visible chunk lists built by cull(), consumed by renderChunks()
int *visibleLists_layer0;
int *visibleLists_layer1;
int visibleCount_layer0;
int visibleCount_layer1;
bool dirtyChunkPresent;
int64_t lastDirtyChunkFound;
static const int FORCE_DIRTY_CHUNK_CHECK_PERIOD_MS = 125; // decreased from 250 to 125 - updated by detectiveren
+2 -2
View File
@@ -491,9 +491,9 @@ void PlayerConnection::handlePlayerAction(shared_ptr<PlayerActionPacket> packet)
}
else if (packet->action == PlayerActionPacket::STOP_DESTROY_BLOCK)
{
player->gameMode->stopDestroyBlock(x, y, z);
bool destroyed = player->gameMode->stopDestroyBlock(x, y, z);
server->getPlayers()->prioritiseTileChanges(x, y, z, level->dimension->id); // 4J added - make sure that the update packets for this get prioritised over other general world updates
if (level->getTile(x, y, z) != 0) player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
if (!destroyed && level->getTile(x, y, z) != 0) player->connection->send(std::make_shared<TileUpdatePacket>(x, y, z, level));
}
else if (packet->action == PlayerActionPacket::ABORT_DESTROY_BLOCK)
{
+4 -1
View File
@@ -172,7 +172,7 @@ void ServerPlayerGameMode::startDestroyBlock(int x, int y, int z, int face)
}
}
void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
bool ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
{
if (x == xDestroyBlock && y == yDestroyBlock && z == zDestroyBlock)
{
@@ -188,6 +188,7 @@ void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
isDestroyingBlock = false;
level->destroyTileProgress(player->entityId, x, y, z, -1);
destroyBlock(x, y, z);
return true;
}
else if (!hasDelayedDestroy)
{
@@ -198,9 +199,11 @@ void ServerPlayerGameMode::stopDestroyBlock(int x, int y, int z)
delayedDestroyY = y;
delayedDestroyZ = z;
delayedTickStart = destroyProgressStart;
return true;
}
}
}
return false;
}
void ServerPlayerGameMode::abortDestroyBlock(int x, int y, int z)
+1 -1
View File
@@ -45,7 +45,7 @@ public:
void tick();
void startDestroyBlock(int x, int y, int z, int face);
void stopDestroyBlock(int x, int y, int z);
bool stopDestroyBlock(int x, int y, int z);
void abortDestroyBlock(int x, int y, int z);
private: