Plasma Engine  2.0
Loading...
Searching...
No Matches
Process_posix.h
1#include <Foundation/FoundationInternal.h>
2PL_FOUNDATION_INTERNAL_HEADER
3
4#include <Foundation/IO/OSFile.h>
5#include <Foundation/Logging/Log.h>
6#include <Foundation/System/Process.h>
7#include <Foundation/Threading/Thread.h>
8#include <Foundation/Threading/ThreadUtils.h>
9
10#include <Foundation/System/SystemInformation.h>
11#include <errno.h>
12#include <fcntl.h>
13#include <poll.h>
14#include <signal.h>
15#include <sys/wait.h>
16#include <unistd.h>
17
18#ifndef _PL_DEFINED_POLLFD_POD
19# define _PL_DEFINED_POLLFD_POD
20PL_DEFINE_AS_POD_TYPE(struct pollfd);
21#endif
22
23class plFd
24{
25public:
26 plFd() = default;
27 plFd(const plFd&) = delete;
28 plFd(plFd&& other)
29 {
30 m_fd = other.m_fd;
31 other.m_fd = -1;
32 }
33
34 ~plFd()
35 {
36 Close();
37 }
38
39 void Close()
40 {
41 if (m_fd != -1)
42 {
43 close(m_fd);
44 m_fd = -1;
45 }
46 }
47
48 bool IsValid() const
49 {
50 return m_fd >= 0;
51 }
52
53 void operator=(const plFd&) = delete;
54 void operator=(plFd&& other)
55 {
56 Close();
57 m_fd = other.m_fd;
58 other.m_fd = -1;
59 }
60
61 void TakeOwnership(int fd)
62 {
63 Close();
64 m_fd = fd;
65 }
66
67 int Borrow() const { return m_fd; }
68
69 int Detach()
70 {
71 auto result = m_fd;
72 m_fd = -1;
73 return result;
74 }
75
76 plResult AddFlags(int addFlags)
77 {
78 if (m_fd < 0)
79 return PL_FAILURE;
80
81 if (addFlags & O_CLOEXEC)
82 {
83 int flags = fcntl(m_fd, F_GETFD);
84 flags |= FD_CLOEXEC;
85 if (fcntl(m_fd, F_SETFD, flags) != 0)
86 {
87 plLog::Error("Failed to set flags on {}: {}", m_fd, errno);
88 return PL_FAILURE;
89 }
90 addFlags &= ~O_CLOEXEC;
91 }
92
93 if (addFlags)
94 {
95 int flags = fcntl(m_fd, F_GETFL);
96 flags |= addFlags;
97 if (fcntl(m_fd, F_SETFD, flags) != 0)
98 {
99 plLog::Error("Failed to set flags on {}: {}", m_fd, errno);
100 return PL_FAILURE;
101 }
102 }
103
104 return PL_SUCCESS;
105 }
106
107 static plResult MakePipe(plFd (&fds)[2], int flags = 0)
108 {
109 fds[0].Close();
110 fds[1].Close();
111#if PL_ENABLED(PL_USE_LINUX_POSIX_EXTENSIONS)
112 if (pipe2((int*)fds, flags) != 0)
113 {
114 return PL_FAILURE;
115 }
116#else
117 if (pipe((int*)fds) != 0)
118 {
119 return PL_FAILURE;
120 }
121 if (flags != 0 && (fds[0].AddFlags(flags).Failed() || fds[1].AddFlags(flags).Failed()))
122 {
123 fds[0].Close();
124 fds[1].Close();
125 return PL_FAILURE;
126 }
127#endif
128 return PL_SUCCESS;
129 }
130
131private:
132 int m_fd = -1;
133};
134
135namespace
136{
137 struct ProcessStartupError
138 {
139 enum class Type : plUInt32
140 {
141 FailedToChangeWorkingDirectory = 0,
142 FailedToExecv = 1
143 };
144
145 Type type;
146 int errorCode;
147 };
148} // namespace
149
150
152{
154 {
155 StopStreamWatcher();
156 }
157
158 pid_t m_childPid = -1;
159 bool m_exitCodeAvailable = false;
160 bool m_processSuspended = false;
161
163 {
164 plFd fd;
165 plDelegate<void(plStringView)> callback;
166 };
168 plDynamicArray<plStringBuilder> m_overflowBuffers;
169 plUniquePtr<plOSThread> m_streamWatcherThread;
170 plFd m_wakeupPipeReadEnd;
171 plFd m_wakeupPipeWriteEnd;
172
173 static void* StreamWatcherThread(void* context)
174 {
175 plProcessImpl* self = reinterpret_cast<plProcessImpl*>(context);
176 char buffer[4096];
177
179
180 pollfds.PushBack({self->m_wakeupPipeReadEnd.Borrow(), POLLIN, 0});
181 for (StdStreamInfo& stream : self->m_streams)
182 {
183 pollfds.PushBack({stream.fd.Borrow(), POLLIN, 0});
184 }
185
186 bool run = true;
187 while (run)
188 {
189 int result = poll(pollfds.GetData(), pollfds.GetCount(), -1);
190 if (result > 0)
191 {
192 // Result at index 0 is special and means there was a WakeUp
193 if (pollfds[0].revents != 0)
194 {
195 run = false;
196 }
197
198 for (plUInt32 i = 1; i < pollfds.GetCount(); ++i)
199 {
200 if (pollfds[i].revents & POLLIN)
201 {
202 plStringBuilder& overflowBuffer = self->m_overflowBuffers[i - 1];
203 StdStreamInfo& stream = self->m_streams[i - 1];
204 while (true)
205 {
206 ssize_t numBytes = read(stream.fd.Borrow(), buffer, PL_ARRAY_SIZE(buffer));
207 if (numBytes < 0)
208 {
209 if (errno == EWOULDBLOCK)
210 {
211 break;
212 }
213 plLog::Error("Process Posix read error on {}: {}", stream.fd.Borrow(), errno);
214 return nullptr;
215 }
216
217 const char* szCurrentPos = buffer;
218 const char* szEndPos = buffer + numBytes;
219 while (szCurrentPos < szEndPos)
220 {
221 const char* szFound = plStringUtils::FindSubString(szCurrentPos, "\n", szEndPos);
222 if (szFound)
223 {
224 if (overflowBuffer.IsEmpty())
225 {
226 // If there is nothing in the overflow buffer this is a complete line and can be fired as is.
227 stream.callback(plStringView(szCurrentPos, szFound + 1));
228 }
229 else
230 {
231 // We have data in the overflow buffer so this is the final part of a partial line so we need to complete and fire the overflow buffer.
232 overflowBuffer.Append(plStringView(szCurrentPos, szFound + 1));
233 stream.callback(overflowBuffer);
234 overflowBuffer.Clear();
235 }
236 szCurrentPos = szFound + 1;
237 }
238 else
239 {
240 // This is either the start or a middle segment of a line, append to overflow buffer.
241 overflowBuffer.Append(plStringView(szCurrentPos, szEndPos));
242 szCurrentPos = szEndPos;
243 }
244 }
245
246 if (numBytes < PL_ARRAY_SIZE(buffer))
247 {
248 break;
249 }
250 }
251 }
252 pollfds[i].revents = 0;
253 }
254 }
255 else if (result < 0)
256 {
257 plLog::Error("poll error {}", errno);
258 break;
259 }
260 }
261
262 for (plUInt32 i = 0; i < self->m_streams.GetCount(); ++i)
263 {
264 plStringBuilder& overflowBuffer = self->m_overflowBuffers[i];
265 if (!overflowBuffer.IsEmpty())
266 {
267 self->m_streams[i].callback(overflowBuffer);
268 overflowBuffer.Clear();
269 }
270
271 self->m_streams[i].fd.Close();
272 }
273
274 return nullptr;
275 }
276
277 plResult StartStreamWatcher()
278 {
279 plFd wakeupPipe[2];
280 if (plFd::MakePipe(wakeupPipe, O_NONBLOCK | O_CLOEXEC).Failed())
281 {
282 plLog::Error("Failed to setup wakeup pipe {}", errno);
283 return PL_FAILURE;
284 }
285 else
286 {
287 m_wakeupPipeReadEnd = std::move(wakeupPipe[0]);
288 m_wakeupPipeWriteEnd = std::move(wakeupPipe[1]);
289 }
290
291 m_streamWatcherThread = PL_DEFAULT_NEW(plOSThread, &StreamWatcherThread, this, "StdStrmWtch");
292 m_streamWatcherThread->Start();
293
294 return PL_SUCCESS;
295 }
296
297 void StopStreamWatcher()
298 {
299 if (m_streamWatcherThread)
300 {
301 char c = 0;
302 PL_IGNORE_UNUSED(write(m_wakeupPipeWriteEnd.Borrow(), &c, 1));
303 m_streamWatcherThread->Join();
304 m_streamWatcherThread = nullptr;
305 }
306 m_wakeupPipeReadEnd.Close();
307 m_wakeupPipeWriteEnd.Close();
308 }
309
310 void AddStream(plFd fd, const plDelegate<void(plStringView)>& callback)
311 {
312 m_streams.PushBack({std::move(fd), callback});
313 m_overflowBuffers.SetCount(m_streams.GetCount());
314 }
315
316 plUInt32 GetNumStreams() const { return m_streams.GetCount(); }
317
318 static plResult StartChildProcess(const plProcessOptions& opt, pid_t& outPid, bool suspended, plFd& outStdOutFd, plFd& outStdErrFd)
319 {
320 plFd stdoutPipe[2];
321 plFd stderrPipe[2];
322 plFd startupErrorPipe[2];
323
324 plStringBuilder executablePath = opt.m_sProcess;
325 plFileStats stats;
326 if (!opt.m_sProcess.IsAbsolutePath())
327 {
328 executablePath = plOSFile::GetCurrentWorkingDirectory();
329 executablePath.AppendPath(opt.m_sProcess);
330 }
331
332 if (plOSFile::GetFileStats(executablePath, stats).Failed() || stats.m_bIsDirectory)
333 {
335 auto envPATH = getenv("PATH");
336 if (envPATH == nullptr) // if no PATH environment variable is available, we need to fetch the system default;
337 {
338#if _POSIX_C_SOURCE >= 2 || _XOPEN_SOURCE
339 size_t confPathSize = confstr(_CS_PATH, nullptr, 0);
340 if (confPathSize > 0)
341 {
342 confPath.SetCountUninitialized(confPathSize);
343 if (confstr(_CS_PATH, confPath.GetData(), confPath.GetCount()) == 0)
344 {
345 confPath.SetCountUninitialized(0);
346 }
347 }
348#endif
349 if (confPath.GetCount() == 0)
350 {
351 confPath.PushBack('\0');
352 }
353 envPATH = confPath.GetData();
354 }
355
356 plStringView path = envPATH;
358 path.Split(false, pathParts, ":");
359
360 for (auto& pathPart : pathParts)
361 {
362 executablePath = pathPart;
363 executablePath.AppendPath(opt.m_sProcess);
364 if (plOSFile::GetFileStats(executablePath, stats).Succeeded() && !stats.m_bIsDirectory)
365 {
366 break;
367 }
368 executablePath.Clear();
369 }
370 }
371
372 if (executablePath.IsEmpty())
373 {
374 return PL_FAILURE;
375 }
376
377 if (opt.m_onStdOut.IsValid())
378 {
379 if (plFd::MakePipe(stdoutPipe).Failed())
380 {
381 return PL_FAILURE;
382 }
383 if (stdoutPipe[0].AddFlags(O_NONBLOCK).Failed())
384 {
385 return PL_FAILURE;
386 }
387 }
388
389 if (opt.m_onStdError.IsValid())
390 {
391 if (plFd::MakePipe(stderrPipe).Failed())
392 {
393 return PL_FAILURE;
394 }
395 if (stderrPipe[0].AddFlags(O_NONBLOCK).Failed())
396 {
397 return PL_FAILURE;
398 }
399 }
400
401 if (plFd::MakePipe(startupErrorPipe, O_CLOEXEC).Failed())
402 {
403 return PL_FAILURE;
404 }
405
406 pid_t childPid = fork();
407 if (childPid < 0)
408 {
409 return PL_FAILURE;
410 }
411
412 if (childPid == 0) // We are the child
413 {
414 if (suspended)
415 {
416 if (raise(SIGSTOP) < 0)
417 {
418 _exit(-1);
419 }
420 }
421
422 if (opt.m_bHideConsoleWindow == true)
423 {
424 // Redirect STDIN to /dev/null
425 int stdinReplace = open("/dev/null", O_RDONLY);
426 dup2(stdinReplace, STDIN_FILENO);
427 close(stdinReplace);
428
429 if (!opt.m_onStdOut.IsValid())
430 {
431 int stdoutReplace = open("/dev/null", O_WRONLY);
432 dup2(stdoutReplace, STDOUT_FILENO);
433 close(stdoutReplace);
434 }
435
436 if (!opt.m_onStdError.IsValid())
437 {
438 int stderrReplace = open("/dev/null", O_WRONLY);
439 dup2(stderrReplace, STDERR_FILENO);
440 close(stderrReplace);
441 }
442 }
443 else
444 {
445 // TODO: Launch a x-terminal-emulator with the command and somehow redirect STDOUT, etc?
446 PL_ASSERT_NOT_IMPLEMENTED;
447 }
448
449 if (opt.m_onStdOut.IsValid())
450 {
451 stdoutPipe[0].Close(); // We don't need the read end of the pipe in the child process
452 dup2(stdoutPipe[1].Borrow(), STDOUT_FILENO); // redirect the write end to STDOUT
453 stdoutPipe[1].Close();
454 }
455
456 if (opt.m_onStdError.IsValid())
457 {
458 stderrPipe[0].Close(); // We don't need the read end of the pipe in the child process
459 dup2(stderrPipe[1].Borrow(), STDERR_FILENO); // redirect the write end to STDERR
460 stderrPipe[1].Close();
461 }
462
463 startupErrorPipe[0].Close(); // we don't need the read end of the startup error pipe in the child process
464
466
467 args.PushBack(const_cast<char*>(executablePath.GetData()));
468 for (const plString& arg : opt.m_Arguments)
469 {
470 args.PushBack(const_cast<char*>(arg.GetData()));
471 }
472 args.PushBack(nullptr);
473
474 if (!opt.m_sWorkingDirectory.IsEmpty())
475 {
476 if (chdir(opt.m_sWorkingDirectory.GetData()) < 0)
477 {
478 auto err = ProcessStartupError{ProcessStartupError::Type::FailedToChangeWorkingDirectory, 0};
479 PL_IGNORE_UNUSED(write(startupErrorPipe[1].Borrow(), &err, sizeof(err)));
480 startupErrorPipe[1].Close();
481 _exit(-1);
482 }
483 }
484
485 if (execv(executablePath, args.GetData()) < 0)
486 {
487 auto err = ProcessStartupError{ProcessStartupError::Type::FailedToExecv, errno};
488 PL_IGNORE_UNUSED(write(startupErrorPipe[1].Borrow(), &err, sizeof(err)));
489 startupErrorPipe[1].Close();
490 _exit(-1);
491 }
492 }
493 else
494 {
495 startupErrorPipe[1].Close(); // We don't need the write end of the startup error pipe in the parent process
496 stdoutPipe[1].Close(); // Don't need the write end in the parent process
497 stderrPipe[1].Close(); // Don't need the write end in the parent process
498
499 ProcessStartupError err = {};
500 auto errSize = read(startupErrorPipe[0].Borrow(), &err, sizeof(err));
501 startupErrorPipe[0].Close(); // we no longer need the read end of the startup error pipe
502
503 // There are two possible cases here
504 // Case 1: errSize is equal to 0, which means no error happened on the startupErrorPipe was closed during the execv call
505 // Case 2: errSize > 0 in which case there was an error before the pipe was closed normally.
506 if (errSize > 0)
507 {
508 PL_ASSERT_DEV(errSize == sizeof(err), "Child process should have written a full ProcessStartupError struct");
509 switch (err.type)
510 {
511 case ProcessStartupError::Type::FailedToChangeWorkingDirectory:
512 plLog::Error("Failed to start process '{}' because the given working directory '{}' is invalid", opt.m_sProcess, opt.m_sWorkingDirectory);
513 break;
514 case ProcessStartupError::Type::FailedToExecv:
515 plLog::Error("Failed to exec when starting process '{}' the error code is '{}'", opt.m_sProcess, err.errorCode);
516 break;
517 }
518 return PL_FAILURE;
519 }
520
521 outPid = childPid;
522
523 if (opt.m_onStdOut.IsValid())
524 {
525 outStdOutFd = std::move(stdoutPipe[0]);
526 }
527
528 if (opt.m_onStdError.IsValid())
529 {
530 outStdErrFd = std::move(stderrPipe[0]);
531 }
532 }
533
534 return PL_SUCCESS;
535 }
536};
537
538plProcess::plProcess()
539{
540 m_pImpl = PL_DEFAULT_NEW(plProcessImpl);
541}
542
543plProcess::~plProcess()
544{
545 if (GetState() == plProcessState::Running)
546 {
547 plLog::Dev("Process still running - terminating '{}'", m_sProcess);
548
549 Terminate().IgnoreResult();
550 }
551
552 // Explicitly clear the implementation here so that member
553 // state (e.g. delegates) used by the impl survives the implementation.
554 m_pImpl.Clear();
555}
556
557plResult plProcess::Execute(const plProcessOptions& opt, plInt32* out_iExitCode /*= nullptr*/)
558{
559 pid_t childPid = 0;
560 plFd stdoutFd;
561 plFd stderrFd;
562 if (plProcessImpl::StartChildProcess(opt, childPid, false, stdoutFd, stderrFd).Failed())
563 {
564 return PL_FAILURE;
565 }
566
567 plProcessImpl impl;
568 if (stdoutFd.IsValid())
569 {
570 impl.AddStream(std::move(stdoutFd), opt.m_onStdOut);
571 }
572
573 if (stderrFd.IsValid())
574 {
575 impl.AddStream(std::move(stderrFd), opt.m_onStdError);
576 }
577
578 if (impl.GetNumStreams() > 0 && impl.StartStreamWatcher().Failed())
579 {
580 return PL_FAILURE;
581 }
582
583 int childStatus = -1;
584 pid_t waitedPid = waitpid(childPid, &childStatus, 0);
585 if (waitedPid < 0)
586 {
587 return PL_FAILURE;
588 }
589 if (out_iExitCode != nullptr)
590 {
591 if (WIFEXITED(childStatus))
592 {
593 *out_iExitCode = WEXITSTATUS(childStatus);
594 }
595 else
596 {
597 *out_iExitCode = -1;
598 }
599 }
600 return PL_SUCCESS;
601}
602
603plResult plProcess::Launch(const plProcessOptions& opt, plBitflags<plProcessLaunchFlags> launchFlags /*= plProcessLaunchFlags::None*/)
604{
605 PL_ASSERT_DEV(m_pImpl->m_childPid == -1, "Can not reuse an instance of plProcess");
606
607 plFd stdoutFd;
608 plFd stderrFd;
609
610 if (plProcessImpl::StartChildProcess(opt, m_pImpl->m_childPid, launchFlags.IsSet(plProcessLaunchFlags::Suspended), stdoutFd, stderrFd).Failed())
611 {
612 return PL_FAILURE;
613 }
614
615 m_pImpl->m_exitCodeAvailable = false;
616 m_pImpl->m_processSuspended = launchFlags.IsSet(plProcessLaunchFlags::Suspended);
617
618 if (stdoutFd.IsValid())
619 {
620 m_pImpl->AddStream(std::move(stdoutFd), opt.m_onStdOut);
621 }
622
623 if (stderrFd.IsValid())
624 {
625 m_pImpl->AddStream(std::move(stderrFd), opt.m_onStdError);
626 }
627
628 if (m_pImpl->GetNumStreams() > 0)
629 {
630 if (m_pImpl->StartStreamWatcher().Failed())
631 {
632 return PL_FAILURE;
633 }
634 }
635
636 if (launchFlags.IsSet(plProcessLaunchFlags::Detached))
637 {
638 Detach();
639 }
640
641 return PL_SUCCESS;
642}
643
644plResult plProcess::ResumeSuspended()
645{
646 if (m_pImpl->m_childPid < 0 || !m_pImpl->m_processSuspended)
647 {
648 return PL_FAILURE;
649 }
650
651 if (kill(m_pImpl->m_childPid, SIGCONT) < 0)
652 {
653 return PL_FAILURE;
654 }
655 m_pImpl->m_processSuspended = false;
656 return PL_SUCCESS;
657}
658
659plResult plProcess::WaitToFinish(plTime timeout /*= plTime::MakeZero()*/)
660{
661 int childStatus = 0;
662 PL_SCOPE_EXIT(m_pImpl->StopStreamWatcher());
663
664 if (timeout.IsZero())
665 {
666 if (waitpid(m_pImpl->m_childPid, &childStatus, 0) < 0)
667 {
668 return PL_FAILURE;
669 }
670 }
671 else
672 {
673 int waitResult = 0;
674 plTime startWait = plTime::Now();
675 while (true)
676 {
677 waitResult = waitpid(m_pImpl->m_childPid, &childStatus, WNOHANG);
678 if (waitResult < 0)
679 {
680 return PL_FAILURE;
681 }
682 if (waitResult > 0)
683 {
684 break;
685 }
686 plTime timeSpent = plTime::Now() - startWait;
687 if (timeSpent > timeout)
688 {
689 return PL_FAILURE;
690 }
692 }
693 }
694
695 if (WIFEXITED(childStatus))
696 {
697 m_iExitCode = WEXITSTATUS(childStatus);
698 }
699 else
700 {
701 m_iExitCode = -1;
702 }
703 m_pImpl->m_exitCodeAvailable = true;
704
705 return PL_SUCCESS;
706}
707
708plResult plProcess::Terminate()
709{
710 if (m_pImpl->m_childPid == -1)
711 {
712 return PL_FAILURE;
713 }
714
715 PL_SCOPE_EXIT(m_pImpl->StopStreamWatcher());
716
717 if (kill(m_pImpl->m_childPid, SIGKILL) < 0)
718 {
719 if (errno != ESRCH) // ESRCH = Process does not exist
720 {
721 return PL_FAILURE;
722 }
723 }
724 m_pImpl->m_exitCodeAvailable = true;
725 m_iExitCode = -1;
726
727 return PL_SUCCESS;
728}
729
730plProcessState plProcess::GetState() const
731{
732 if (m_pImpl->m_childPid == -1)
733 {
734 return plProcessState::NotStarted;
735 }
736
737 if (m_pImpl->m_exitCodeAvailable)
738 {
739 return plProcessState::Finished;
740 }
741
742 int childStatus = -1;
743 int waitResult = waitpid(m_pImpl->m_childPid, &childStatus, WNOHANG);
744 if (waitResult > 0)
745 {
746 m_iExitCode = WEXITSTATUS(childStatus);
747 m_pImpl->m_exitCodeAvailable = true;
748
749 m_pImpl->StopStreamWatcher();
750
751 return plProcessState::Finished;
752 }
753
754 return plProcessState::Running;
755}
756
757void plProcess::Detach()
758{
759 m_pImpl->m_childPid = -1;
760}
761
762plOsProcessHandle plProcess::GetProcessHandle() const
763{
764 PL_ASSERT_DEV(false, "There is no process handle on posix");
765 return nullptr;
766}
767
768plOsProcessID plProcess::GetProcessID() const
769{
770 PL_ASSERT_DEV(m_pImpl->m_childPid != -1, "No ProcessID available");
771 return m_pImpl->m_childPid;
772}
773
774plOsProcessID plProcess::GetCurrentProcessID()
775{
776 return getpid();
777}
void PushBack(const T &value)
Pushes value at the end of the array.
Definition ArrayBase_inl.h:333
T * GetData()
Returns a pointer to the array data, or nullptr if the array is empty.
Definition ArrayBase_inl.h:423
void SetCountUninitialized(plUInt32 uiCount)
Resizes the array to have exactly uiCount elements. Extra elements might be uninitialized.
Definition ArrayBase_inl.h:155
void SetCount(plUInt32 uiCount)
Resizes the array to have exactly uiCount elements. Default constructs extra elements if the array is...
Definition ArrayBase_inl.h:106
plUInt32 GetCount() const
Returns the number of active elements in the array.
Definition ArrayBase_inl.h:172
Definition DynamicArray.h:81
Definition Process_posix.h:24
A hybrid array uses in-place storage to handle the first few elements without any allocation....
Definition HybridArray.h:12
static void Dev(plLogInterface *pInterface, const plFormatString &string)
Status information that is nice to have during development.
static void Error(plLogInterface *pInterface, const plFormatString &string)
An error that needs to be fixed as soon as possible.
Definition Log.cpp:375
static plResult GetFileStats(plStringView sFileOrFolder, plFileStats &out_stats)
Gets the stats about the given file or folder. Returns false, if the stats could not be determined.
static const plString GetCurrentWorkingDirectory()
Returns the processes current working directory (CWD).
Implementation of a thread.
Definition OSThread.h:13
plStringBuilder is a class that is meant for creating and modifying strings.
Definition StringBuilder.h:35
void Clear()
Resets this string to be empty. Does not deallocate any previously allocated data,...
Definition StringBuilder_inl.h:88
const char * GetData() const
Returns a char pointer to the internal Utf8 data.
Definition StringBuilder_inl.h:148
void Append(plUInt32 uiChar)
Appends a single Utf32 character.
Definition StringBuilder_inl.h:94
void AppendPath(plStringView sPath1, plStringView sPath2={}, plStringView sPath3={}, plStringView sPath4={})
Appends several path pieces. Makes sure they are always properly separated by a slash.
Definition StringBuilder.cpp:866
static const char * FindSubString(const char *szSource, const char *szStringToFind, const char *pSourceEnd=plUnicodeUtils::GetMaxStringEnd< char >(), const char *szStringToFindEnd=plUnicodeUtils::GetMaxStringEnd< char >())
Searches for the first occurrence of szStringToFind in szSource.
Definition StringUtils.cpp:550
plStringView represent a read-only sub-string of a larger string, as it can store a dedicated string ...
Definition StringView.h:34
void Split(bool bReturnEmptyStrings, Container &ref_output, const char *szSeparator1, const char *szSeparator2=nullptr, const char *szSeparator3=nullptr, const char *szSeparator4=nullptr, const char *szSeparator5=nullptr, const char *szSeparator6=nullptr) const
Fills the given container with plStringView's which represent each found substring....
Definition StringView_inl.h:141
static void Sleep(const plTime &duration)
Suspends the execution of the current thread for the given amount of time. (Precision may vary accord...
Definition ThreadUtils_Posix.h:29
A Unique ptr manages an object and destroys that object when it goes out of scope....
Definition UniquePtr.h:10
constexpr PL_ALWAYS_INLINE T Min(T f1, T f2)
Returns the smaller value, f1 or f2.
Definition Math_inl.h:27
The plBitflags class allows you to work with type-safe bitflags.
Definition Bitflags.h:82
PL_ALWAYS_INLINE bool IsSet(Enum flag) const
Checks if certain flags are set within the bitfield.
Definition Bitflags.h:127
A generic delegate class which supports static functions and member functions.
Definition Delegate.h:76
Holds the stats for a file.
Definition OSFile.h:34
bool m_bIsDirectory
Whether the file object is a file or folder.
Definition OSFile.h:56
Definition Process_posix.h:163
Definition Process_posix.h:152
Default enum for returning failure or success, instead of using a bool.
Definition Types.h:54
bool IsEmpty() const
Returns whether the string is an empty string.
Definition StringBase_inl.h:25
The time class encapsulates a double value storing the time in seconds.
Definition Time.h:12
PL_ALWAYS_INLINE static constexpr plTime MakeFromMilliseconds(double fMilliseconds)
Creates an instance of plTime that was initialized from milliseconds.
Definition Time.h:26
PL_ALWAYS_INLINE constexpr bool IsZero() const
Returns true if the stored time is exactly zero. That typically means the value was not changed from ...
Definition Time.h:50
static plTime Now()
Gets the current time.
Definition Time_Posix.h:12