Panda3D
Loading...
Searching...
No Matches
vertexDataSaveFile.cxx
Go to the documentation of this file.
1/**
2 * PANDA 3D SOFTWARE
3 * Copyright (c) Carnegie Mellon University. All rights reserved.
4 *
5 * All use of this software is subject to the terms of the revised BSD
6 * license. You should have received a copy of this license along
7 * with this source code in a file named "LICENSE."
8 *
9 * @file vertexDataSaveFile.cxx
10 * @author drose
11 * @date 2007-05-12
12 */
13
14#include "vertexDataSaveFile.h"
15#include "mutexHolder.h"
16#include "clockObject.h"
17#include "config_gobj.h"
18
19#ifndef _WIN32
20#include <sys/types.h>
21#include <sys/stat.h>
22#include <fcntl.h>
23#include <errno.h>
24#endif // _WIN32
25
26#if defined(__ANDROID__) && !defined(PHAVE_LOCKF)
27// Needed for flock.
28#include <sys/file.h>
29#endif
30
31using std::dec;
32using std::hex;
33
34/**
35 *
36 */
37VertexDataSaveFile::
38VertexDataSaveFile(const Filename &directory, const std::string &prefix,
39 size_t max_size) :
40 SimpleAllocator(max_size, _lock)
41{
42 Filename dir;
43 if (directory.empty()) {
45 } else {
46 dir = directory;
47 }
48
49 _is_valid = false;
50 _total_file_size = 0;
51
52 // Try to open and lock a writable temporary filename.
53 int index = 0;
54 while (true) {
55 ++index;
56 std::ostringstream strm;
57 strm << prefix << "_" << index << ".dat";
58
59 std::string basename = strm.str();
60 _filename = Filename(dir, basename);
61 std::string os_specific = _filename.to_os_specific();
62
63 if (gobj_cat.is_debug()) {
64 gobj_cat.debug()
65 << "Creating vertex data save file " << os_specific << "\n";
66 }
67
68#ifdef _WIN32
69 // Windows case.
70 DWORD flags = FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE | FILE_FLAG_RANDOM_ACCESS;
71#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
72 // In SIMPLE_THREADS mode, we use "overlapped" IO.
73 flags |= FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING;
74#endif
75 _handle = CreateFile(os_specific.c_str(), GENERIC_READ | GENERIC_WRITE,
76 0, nullptr, CREATE_ALWAYS, flags, nullptr);
77 if (_handle != INVALID_HANDLE_VALUE) {
78 // The file was successfully opened and locked.
79 break;
80
81 } else {
82 // Couldn't open the file. Either the directory was bad, or the file
83 // was already locked by another.
84 DWORD err = GetLastError();
85
86 if (err != ERROR_SHARING_VIOLATION) {
87 // File couldn't be opened; permission problem or bogus directory.
88 if (!dir.empty()) {
89 // Try the current directory, once.
90 dir = Filename();
91 } else {
92 gobj_cat.error()
93 << "Couldn't open vertex data save file.\n";
94 return;
95 }
96 }
97
98 // Couldn't lock the file. Try the next one.
99 }
100
101#else
102 // Posix case.
103 int flags = O_RDWR | O_CREAT;
104#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
105 // In SIMPLE_THREADS mode, we use non-blocking IO.
106 flags |= O_NONBLOCK;
107#endif
108
109 _fd = ::open(os_specific.c_str(), flags, 0666);
110 while (_fd == -1 && errno == EAGAIN) {
112 _fd = ::open(os_specific.c_str(), flags, 0666);
113 }
114
115 if (_fd == -1) {
116 // Couldn't open the file: permissions problem or bad directory.
117 if (!_filename.exists()) {
118 // It must be a bad directory.
119 if (!dir.empty()) {
120 // Try the current directory, once.
121 dir = Filename();
122 } else {
123 gobj_cat.error()
124 << "Couldn't open vertex data save file.\n";
125 return;
126 }
127 }
128
129 // If it's a permissions problem, it might be a user-level permissions
130 // issue. Continue to the next.
131 continue;
132 }
133
134 // Now try to lock the file, so we can be sure that no other process is
135 // simultaneously writing to the same save file.
136#ifdef PHAVE_LOCKF
137 int result = lockf(_fd, F_TLOCK, 0);
138#else
139 int result = flock(_fd, LOCK_EX | LOCK_NB);
140#endif
141 if (result == 0) {
142 // We've got the file. Truncate it first, for good measure, in case
143 // there's an old version of the file we picked up.
144 if (ftruncate(_fd, 0) < 0) {
145 gobj_cat.warning()
146 << "Couldn't truncate vertex data save file.\n";
147 }
148
149 // On Unix, it's safe to unlink (delete) the temporary file after it's
150 // been opened. The file remains open, but disappears from the
151 // directory. This is kind of like DELETE_ON_CLOSE, to ensure the
152 // temporary file won't accidentally get left behind, except it's a
153 // little more proactive.
154 unlink(os_specific.c_str());
155 _filename = Filename();
156 break;
157 }
158
159 // Try the next file.
160 close(_fd);
161#endif // _WIN32
162 }
163
164 _is_valid = true;
165}
166
167/**
168 *
169 */
170VertexDataSaveFile::
171~VertexDataSaveFile() {
172#ifdef _WIN32
173 if (_handle != nullptr) {
174 CloseHandle(_handle);
175 }
176#else
177 if (_fd != -1) {
178 close(_fd);
179 }
180#endif // _WIN32
181
182 // No need to remove the file, since in both above cases we have already
183 // removed it. And removing it now, after we have closed and unlocked it,
184 // might accidentally remove someone else's copy.
185 /*
186 if (!_filename.empty()) {
187 _filename.unlink();
188 }
189 */
190}
191
192/**
193 * Writes a block of data to the file, and returns a handle to the block
194 * handle. Returns NULL if the data cannot be written (e.g. no remaining
195 * space on the file).
196 */
197PT(VertexDataSaveBlock) VertexDataSaveFile::
198write_data(const unsigned char *data, size_t size, bool compressed) {
199 MutexHolder holder(_lock);
200
201 if (!_is_valid) {
202 return nullptr;
203 }
204
205 PT(VertexDataSaveBlock) block = (VertexDataSaveBlock *)SimpleAllocator::do_alloc(size);
206 if (block != nullptr) {
207 _total_file_size = std::max(_total_file_size, block->get_start() + size);
208 block->set_compressed(compressed);
209
210#ifdef _WIN32
211 OVERLAPPED overlapped;
212 memset(&overlapped, 0, sizeof(overlapped));
213 overlapped.Offset = block->get_start();
214
215 DWORD bytes_written = 0;
216 double start_time = ClockObject::get_global_clock()->get_real_time();
217 int num_passes = 0;
218 BOOL success = WriteFile(_handle, data, size, &bytes_written, &overlapped);
219 while (!success) {
220 DWORD error = GetLastError();
221 if (error == ERROR_IO_INCOMPLETE || error == ERROR_IO_PENDING) {
222 // Wait for more later.
224 ++num_passes;
225 } else {
226 gobj_cat.error()
227 << "Error writing " << size
228 << " bytes to save file, windows error code 0x" << hex
229 << error << dec << ". Disk full?\n";
230 return nullptr;
231 }
232 success = GetOverlappedResult(_handle, &overlapped, &bytes_written, false);
233 }
234 nassertr(bytes_written == size, nullptr);
235 double finish_time = ClockObject::get_global_clock()->get_real_time();
236 if (gobj_cat.is_debug()) {
237 gobj_cat.debug()
238 << "Wrote " << size << " bytes in " << *Thread::get_current_thread() << " over " << floor((finish_time - start_time) * 1000.0) << " ms and " << num_passes << " passes.\n";
239 }
240#else
241 // Posix case.
242 if (lseek(_fd, block->get_start(), SEEK_SET) == -1) {
243 gobj_cat.error()
244 << "Error seeking to position " << block->get_start() << " in save file.\n";
245 return nullptr;
246 }
247
248 while (size > 0) {
249 ssize_t result = ::write(_fd, data, size);
250 if (result < 0) {
251 if (errno == EAGAIN) {
253 } else {
254 gobj_cat.error()
255 << "Error writing " << size << " bytes to save file. Disk full?\n";
256 return nullptr;
257 }
258 continue;
259 }
260
262 data += result;
263 size -= result;
264 }
265#endif // _WIN32
266 }
267
268 return block;
269}
270
271/**
272 * Reads a block of data from the file, and returns true on success, false on
273 * failure.
274 */
276read_data(unsigned char *data, size_t size, VertexDataSaveBlock *block) {
277 MutexHolder holder(_lock);
278
279 if (!_is_valid) {
280 return false;
281 }
282
283 nassertr(size == block->get_size(), false);
284
285 /*
286 static ConfigVariableBool allow_mainthread_read("allow-mainthread-read", 1);
287 if (!allow_mainthread_read) {
288 nassertr(Thread::get_current_thread() != Thread::get_main_thread(), false);
289 }
290 */
291
292#ifdef _WIN32
293 OVERLAPPED overlapped;
294 memset(&overlapped, 0, sizeof(overlapped));
295 overlapped.Offset = block->get_start();
296
297 DWORD bytes_read = 0;
298 double start_time = ClockObject::get_global_clock()->get_real_time();
299 int num_passes = 0;
300 BOOL success = ReadFile(_handle, data, size, &bytes_read, &overlapped);
301 while (!success) {
302 DWORD error = GetLastError();
303 if (error == ERROR_IO_INCOMPLETE || error == ERROR_IO_PENDING) {
304 // Wait for more later.
306 ++num_passes;
307 } else {
308 gobj_cat.error()
309 << "Error reading " << size
310 << " bytes from save file, windows error code 0x" << hex
311 << error << dec << ".\n";
312 return false;
313 }
314 success = GetOverlappedResult(_handle, &overlapped, &bytes_read, false);
315 }
316 nassertr(bytes_read == size, false);
317 double finish_time = ClockObject::get_global_clock()->get_real_time();
318 if (gobj_cat.is_debug()) {
319 gobj_cat.debug()
320 << "Read " << size << " bytes in " << *Thread::get_current_thread() << " over " << floor((finish_time - start_time) * 1000.0) << " ms and " << num_passes << " passes.\n";
321 }
322
323#else
324 // Posix case.
325 if (lseek(_fd, block->get_start(), SEEK_SET) == -1) {
326 gobj_cat.error()
327 << "Error seeking to position " << block->get_start() << " in save file.\n";
328 return false;
329 }
330 while (size > 0) {
331 ssize_t result = read(_fd, data, size);
332 if (result == -1) {
333 if (errno == EAGAIN) {
335 } else {
336 gobj_cat.error()
337 << "Error reading " << size << " bytes from save file.\n";
338 return false;
339 }
340 }
341
343 data += result;
344 size -= result;
345 }
346#endif // _WIN32
347
348 return true;
349}
350
351/**
352 * Creates a new SimpleAllocatorBlock object. Override this function to
353 * specialize the block type returned.
354 */
355SimpleAllocatorBlock *VertexDataSaveFile::
356make_block(size_t start, size_t size) {
357 return new VertexDataSaveBlock(this, start, size);
358}
get_real_time
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition clockObject.h:92
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
The name of a file, such as a texture file or an Egg file.
Definition filename.h:44
static const Filename & get_temp_directory()
Returns a path to a system-defined temporary directory.
Definition filename.cxx:539
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition mutexHolder.h:25
A single block as returned from SimpleAllocator::alloc().
size_t get_size() const
Returns the size of this block.
size_t get_start() const
Returns the starting point of this block.
An implementation of a very simple block allocator.
static void consider_yield()
Possibly suspends the current thread for the rest of the current epoch, if it has run for enough this...
Definition thread.I:212
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition thread.h:109
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition thread.I:201
A block of bytes on the save file.
bool read_data(unsigned char *data, size_t size, VertexDataSaveBlock *block)
Reads a block of data from the file, and returns true on success, false on failure.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.