open_toontown_panda3d/panda/src/gobj/vertexDataSaveFile.cxx

351 lines
9.8 KiB
C++

/**
* PANDA 3D SOFTWARE
* Copyright (c) Carnegie Mellon University. All rights reserved.
*
* All use of this software is subject to the terms of the revised BSD
* license. You should have received a copy of this license along
* with this source code in a file named "LICENSE."
*
* @file vertexDataSaveFile.cxx
* @author drose
* @date 2007-05-12
*/
#include "vertexDataSaveFile.h"
#include "mutexHolder.h"
#include "clockObject.h"
#include "config_gobj.h"
#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#endif // _WIN32
/**
*
*/
VertexDataSaveFile::
VertexDataSaveFile(const Filename &directory, const string &prefix,
size_t max_size) :
SimpleAllocator(max_size, _lock)
{
Filename dir;
if (directory.empty()) {
dir = Filename::get_temp_directory();
} else {
dir = directory;
}
_is_valid = false;
_total_file_size = 0;
// Try to open and lock a writable temporary filename.
int index = 0;
while (true) {
++index;
ostringstream strm;
strm << prefix << "_" << index << ".dat";
string basename = strm.str();
_filename = Filename(dir, basename);
string os_specific = _filename.to_os_specific();
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Creating vertex data save file " << os_specific << "\n";
}
#ifdef _WIN32
// Windows case.
DWORD flags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_RANDOM_ACCESS;
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
// In SIMPLE_THREADS mode, we use "overlapped" IO.
flags |= FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING;
#endif
_handle = CreateFile(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
0, NULL, CREATE_ALWAYS, flags, NULL);
if (_handle != INVALID_HANDLE_VALUE) {
// The file was successfully opened and locked.
break;
} else {
// Couldn't open the file. Either the directory was bad, or the file
// was already locked by another.
DWORD err = GetLastError();
if (err != ERROR_SHARING_VIOLATION) {
// File couldn't be opened; permission problem or bogus directory.
if (!dir.empty()) {
// Try the current directory, once.
dir = Filename();
} else {
gobj_cat.error()
<< "Couldn't open vertex data save file.\n";
return;
}
}
// Couldn't lock the file. Try the next one.
}
#else
// Posix case.
int flags = O_RDWR | O_CREAT;
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
// In SIMPLE_THREADS mode, we use non-blocking IO.
flags |= O_NONBLOCK;
#endif
_fd = ::open(os_specific.c_str(), flags, 0666);
while (_fd == -1 && errno == EAGAIN) {
Thread::force_yield();
_fd = ::open(os_specific.c_str(), flags, 0666);
}
if (_fd == -1) {
// Couldn't open the file: permissions problem or bad directory.
if (!_filename.exists()) {
// It must be a bad directory.
if (!dir.empty()) {
// Try the current directory, once.
dir = Filename();
} else {
gobj_cat.error()
<< "Couldn't open vertex data save file.\n";
return;
}
}
// If it's a permissions problem, it might be a user-level permissions
// issue. Continue to the next.
continue;
}
// Now try to lock the file, so we can be sure that no other process is
// simultaneously writing to the same save file.
#ifdef HAVE_LOCKF
int result = lockf(_fd, F_TLOCK, 0);
#else
int result = flock(_fd, LOCK_EX | LOCK_NB);
#endif
if (result == 0) {
// We've got the file. Truncate it first, for good measure, in case
// there's an old version of the file we picked up.
if (ftruncate(_fd, 0) < 0) {
gobj_cat.warning()
<< "Couldn't truncate vertex data save file.\n";
}
// On Unix, it's safe to unlink (delete) the temporary file after it's
// been opened. The file remains open, but disappears from the
// directory. This is kind of like DELETE_ON_CLOSE, to ensure the
// temporary file won't accidentally get left behind, except it's a
// little more proactive.
unlink(os_specific.c_str());
_filename = Filename();
break;
}
// Try the next file.
close(_fd);
#endif // _WIN32
}
_is_valid = true;
}
/**
*
*/
VertexDataSaveFile::
~VertexDataSaveFile() {
#ifdef _WIN32
if (_handle != NULL) {
CloseHandle(_handle);
}
#else
if (_fd != -1) {
close(_fd);
}
#endif // _WIN32
// No need to remove the file, since in both above cases we have already
// removed it. And removing it now, after we have closed and unlocked it,
// might accidentally remove someone else's copy.
/*
if (!_filename.empty()) {
_filename.unlink();
}
*/
}
/**
* Writes a block of data to the file, and returns a handle to the block
* handle. Returns NULL if the data cannot be written (e.g. no remaining
* space on the file).
*/
PT(VertexDataSaveBlock) VertexDataSaveFile::
write_data(const unsigned char *data, size_t size, bool compressed) {
MutexHolder holder(_lock);
if (!_is_valid) {
return NULL;
}
PT(VertexDataSaveBlock) block = (VertexDataSaveBlock *)SimpleAllocator::do_alloc(size);
if (block != (VertexDataSaveBlock *)NULL) {
_total_file_size = max(_total_file_size, block->get_start() + size);
block->set_compressed(compressed);
#ifdef _WIN32
OVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(overlapped));
overlapped.Offset = block->get_start();
DWORD bytes_written = 0;
double start_time = ClockObject::get_global_clock()->get_real_time();
int num_passes = 0;
BOOL success = WriteFile(_handle, data, size, &bytes_written, &overlapped);
while (!success) {
DWORD error = GetLastError();
if (error == ERROR_IO_INCOMPLETE || error == ERROR_IO_PENDING) {
// Wait for more later.
Thread::force_yield();
++num_passes;
} else {
gobj_cat.error()
<< "Error writing " << size
<< " bytes to save file, windows error code 0x" << hex
<< error << dec << ". Disk full?\n";
return NULL;
}
success = GetOverlappedResult(_handle, &overlapped, &bytes_written, false);
}
nassertr(bytes_written == size, NULL);
double finish_time = ClockObject::get_global_clock()->get_real_time();
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Wrote " << size << " bytes in " << *Thread::get_current_thread() << " over " << floor((finish_time - start_time) * 1000.0) << " ms and " << num_passes << " passes.\n";
}
#else
// Posix case.
if (lseek(_fd, block->get_start(), SEEK_SET) == -1) {
gobj_cat.error()
<< "Error seeking to position " << block->get_start() << " in save file.\n";
return NULL;
}
while (size > 0) {
ssize_t result = ::write(_fd, data, size);
if (result < 0) {
if (errno == EAGAIN) {
Thread::force_yield();
} else {
gobj_cat.error()
<< "Error writing " << size << " bytes to save file. Disk full?\n";
return NULL;
}
continue;
}
Thread::consider_yield();
data += result;
size -= result;
}
#endif // _WIN32
}
return block;
}
/**
* Reads a block of data from the file, and returns true on success, false on
* failure.
*/
bool VertexDataSaveFile::
read_data(unsigned char *data, size_t size, VertexDataSaveBlock *block) {
MutexHolder holder(_lock);
if (!_is_valid) {
return false;
}
nassertr(size == block->get_size(), false);
/*
static ConfigVariableBool allow_mainthread_read("allow-mainthread-read", 1);
if (!allow_mainthread_read) {
nassertr(Thread::get_current_thread() != Thread::get_main_thread(), false);
}
*/
#ifdef _WIN32
OVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(overlapped));
overlapped.Offset = block->get_start();
DWORD bytes_read = 0;
double start_time = ClockObject::get_global_clock()->get_real_time();
int num_passes = 0;
BOOL success = ReadFile(_handle, data, size, &bytes_read, &overlapped);
while (!success) {
DWORD error = GetLastError();
if (error == ERROR_IO_INCOMPLETE || error == ERROR_IO_PENDING) {
// Wait for more later.
Thread::force_yield();
++num_passes;
} else {
gobj_cat.error()
<< "Error reading " << size
<< " bytes from save file, windows error code 0x" << hex
<< error << dec << ".\n";
return NULL;
}
success = GetOverlappedResult(_handle, &overlapped, &bytes_read, false);
}
nassertr(bytes_read == size, NULL);
double finish_time = ClockObject::get_global_clock()->get_real_time();
if (gobj_cat.is_debug()) {
gobj_cat.debug()
<< "Read " << size << " bytes in " << *Thread::get_current_thread() << " over " << floor((finish_time - start_time) * 1000.0) << " ms and " << num_passes << " passes.\n";
}
#else
// Posix case.
if (lseek(_fd, block->get_start(), SEEK_SET) == -1) {
gobj_cat.error()
<< "Error seeking to position " << block->get_start() << " in save file.\n";
return false;
}
while (size > 0) {
ssize_t result = read(_fd, data, size);
if (result == -1) {
if (errno == EAGAIN) {
Thread::force_yield();
} else {
gobj_cat.error()
<< "Error reading " << size << " bytes from save file.\n";
return false;
}
}
Thread::consider_yield();
data += result;
size -= result;
}
#endif // _WIN32
return true;
}
/**
* Creates a new SimpleAllocatorBlock object. Override this function to
* specialize the block type returned.
*/
SimpleAllocatorBlock *VertexDataSaveFile::
make_block(size_t start, size_t size) {
return new VertexDataSaveBlock(this, start, size);
}