Panda3D

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
31 using std::dec;
32 using std::hex;
33
34 /**
35 *
36 */
37 VertexDataSaveFile::
38 VertexDataSaveFile(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()) {
44 dir = Filename::get_temp_directory();
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) {
111 Thread::force_yield();
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 */
170 VertexDataSaveFile::
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 */
197 PT(VertexDataSaveBlock) VertexDataSaveFile::
198 write_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.
223 Thread::force_yield();
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) {
252 Thread::force_yield();
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
261 Thread::consider_yield();
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 */
275 bool VertexDataSaveFile::
276 read_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.
305 Thread::force_yield();
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) {
334 Thread::force_yield();
335 } else {
336 gobj_cat.error()
337 << "Error reading " << size << " bytes from save file.\n";
338 return false;
339 }
340 }
341
342 Thread::consider_yield();
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 */
355 SimpleAllocatorBlock *VertexDataSaveFile::
356 make_block(size_t start, size_t size) {
357 return new VertexDataSaveBlock(this, start, size);
358 }
ClockObject::get_real_time
get_real_time
Returns the actual number of seconds elapsed since the ClockObject was created, or since it was last ...
Definition: clockObject.h:92
ClockObject::get_global_clock
static ClockObject * get_global_clock()
Returns a pointer to the global ClockObject.
Definition: clockObject.I:215
Filename
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
Filename::get_temp_directory
static const Filename & get_temp_directory()
Returns a path to a system-defined temporary directory.
Definition: filename.cxx:539
MutexHolder
A lightweight C++ object whose constructor calls acquire() and whose destructor calls release() on a ...
Definition: mutexHolder.h:25
SimpleAllocatorBlock
A single block as returned from SimpleAllocator::alloc().
Definition: simpleAllocator.h:90
SimpleAllocatorBlock::get_size
size_t get_size() const
Returns the size of this block.
Definition: simpleAllocator.I:234
SimpleAllocatorBlock::get_start
size_t get_start() const
Returns the starting point of this block.
Definition: simpleAllocator.I:224
SimpleAllocator
An implementation of a very simple block allocator.
Definition: simpleAllocator.h:29
Thread::consider_yield
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
Thread::get_current_thread
get_current_thread
Returns a pointer to the currently-executing Thread object.
Definition: thread.h:109
Thread::force_yield
static void force_yield()
Suspends the current thread for the rest of the current epoch.
Definition: thread.I:201
VertexDataSaveBlock
A block of bytes on the save file.
Definition: vertexDataSaveFile.h:73
VertexDataSaveFile::read_data
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.
Definition: vertexDataSaveFile.cxx:276
clockObject.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
config_gobj.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
mutexHolder.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
PT
PT(VertexDataSaveBlock) VertexDataSaveFile
Writes a block of data to the file, and returns a handle to the block handle.
Definition: vertexDataSaveFile.cxx:197
vertexDataSaveFile.h
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.