Process polling api
This commit is contained in:
@@ -34,11 +34,20 @@ bool IsFile(String path);
|
||||
String GetWorkingDir(Allocator arena);
|
||||
bool IsAbsolute(String path);
|
||||
|
||||
struct StdoutPollInfo {
|
||||
int64_t size_read;
|
||||
int64_t size_available;
|
||||
};
|
||||
|
||||
struct Process {
|
||||
bool is_valid;
|
||||
String error_message;
|
||||
char platform[32];
|
||||
int exit_code;
|
||||
char platform[6 * 8];
|
||||
};
|
||||
|
||||
Process RunEx(String cmd);
|
||||
int Wait(Process *process);
|
||||
Process RunCmd(String command_line, String working_dir);
|
||||
bool WaitForExit(Process *process);
|
||||
bool PollExitCode(Process *process);
|
||||
void KillProcess(Process *process);
|
||||
StdoutPollInfo PollStdout(Process *process, char *buffer, int64_t buffer_size);
|
||||
@@ -35,57 +35,6 @@ bool VDecommit(void *p, size_t size) {
|
||||
}
|
||||
// Basic end
|
||||
|
||||
Process RunEx(String args) {
|
||||
Scratch scratch;
|
||||
wchar_t *application_name = NULL;
|
||||
wchar_t *cmd = ToWidechar(scratch, args);
|
||||
BOOL inherit_handles = FALSE;
|
||||
DWORD creation_flags = 0;
|
||||
void *enviroment = NULL;
|
||||
wchar_t *working_dir = NULL;
|
||||
STARTUPINFOW startup_info = {};
|
||||
startup_info.cb = sizeof(STARTUPINFOW);
|
||||
Process result = {};
|
||||
Assert(sizeof(result.platform) >= sizeof(PROCESS_INFORMATION));
|
||||
PROCESS_INFORMATION *process_info = (PROCESS_INFORMATION *)result.platform;
|
||||
BOOL success = CreateProcessW(application_name, cmd, NULL, NULL, inherit_handles, creation_flags, enviroment, working_dir, &startup_info, process_info);
|
||||
result.is_valid = true;
|
||||
if (!success) {
|
||||
result.is_valid = false;
|
||||
|
||||
LPVOID lpMsgBuf;
|
||||
DWORD dw = GetLastError();
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
|
||||
|
||||
char *buff = (char *)lpMsgBuf;
|
||||
size_t buffLen = strlen((const char *)buff);
|
||||
if (buffLen > 0 && buff[buffLen - 1] == '\n') {
|
||||
buff[buffLen - 1] = 0;
|
||||
}
|
||||
|
||||
// @warning: leak! but we don't care
|
||||
result.error_message = Format(GetSystemAllocator(), "Failed to create process | message: %s | cmd: %.*s", (char *)lpMsgBuf, FmtString(args));
|
||||
LocalFree(lpMsgBuf);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int Wait(Process *process) {
|
||||
Assert(process->is_valid);
|
||||
PROCESS_INFORMATION *pi = (PROCESS_INFORMATION *)process->platform;
|
||||
WaitForSingleObject(pi->hProcess, INFINITE);
|
||||
|
||||
DWORD exit_code;
|
||||
BOOL err = GetExitCodeProcess(pi->hProcess, &exit_code);
|
||||
Assert(err != 0);
|
||||
|
||||
CloseHandle(pi->hProcess);
|
||||
CloseHandle(pi->hThread);
|
||||
process[0] = {};
|
||||
return (int)exit_code;
|
||||
}
|
||||
|
||||
bool InitOS() {
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
|
||||
@@ -324,3 +273,179 @@ bool DeleteFile(String path) {
|
||||
if (success == 0) result = false;
|
||||
return result;
|
||||
}
|
||||
|
||||
struct Win32Process {
|
||||
HANDLE handle;
|
||||
HANDLE child_stdout_read;
|
||||
HANDLE child_stdout_write;
|
||||
HANDLE child_stdin_read;
|
||||
HANDLE child_stdin_write;
|
||||
};
|
||||
static_assert(sizeof(Win32Process) < sizeof(Process::platform));
|
||||
|
||||
static String Win32GetPlatformError(String cmd) {
|
||||
LPVOID lpMsgBuf;
|
||||
DWORD dw = GetLastError();
|
||||
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL);
|
||||
|
||||
char *buff = (char *)lpMsgBuf;
|
||||
size_t buffLen = strlen((const char *)buff);
|
||||
if (buffLen > 0 && buff[buffLen - 1] == '\n') {
|
||||
buff[buffLen - 1] = 0;
|
||||
}
|
||||
|
||||
// @warning: leak! but we don't care
|
||||
String r = Format(GetSystemAllocator(), "Failed to create process using cmd: %.*s! %s", FmtString(cmd), (char *)lpMsgBuf);
|
||||
LocalFree(lpMsgBuf);
|
||||
return r;
|
||||
}
|
||||
|
||||
static void Win32CloseProcess(Process *process) {
|
||||
Win32Process *p = (Win32Process *)process->platform;
|
||||
if (p->handle != INVALID_HANDLE_VALUE) CloseHandle(p->handle);
|
||||
if (p->child_stdout_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_write);
|
||||
if (p->child_stdout_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdout_read);
|
||||
if (p->child_stdin_read != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_read);
|
||||
if (p->child_stdin_write != INVALID_HANDLE_VALUE) CloseHandle(p->child_stdin_write);
|
||||
process->is_valid = false;
|
||||
*p = {};
|
||||
}
|
||||
|
||||
static void Win32ProcessError(Process *process, String cmd) {
|
||||
Win32Process *p = (Win32Process *)process->platform;
|
||||
process->error_message = Win32GetPlatformError(cmd);
|
||||
Win32CloseProcess(process);
|
||||
}
|
||||
|
||||
Process RunCmd(String command_line, String working_dir) {
|
||||
Process process = {};
|
||||
Win32Process *p = (Win32Process *)process.platform;
|
||||
|
||||
Scratch scratch;
|
||||
command_line = Format(scratch, "/C %.*s", FmtString(command_line));
|
||||
|
||||
p->handle = INVALID_HANDLE_VALUE;
|
||||
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
||||
p->child_stdout_read = INVALID_HANDLE_VALUE;
|
||||
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
||||
p->child_stdin_write = INVALID_HANDLE_VALUE;
|
||||
|
||||
SECURITY_ATTRIBUTES security_atrb = {};
|
||||
security_atrb.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
security_atrb.bInheritHandle = TRUE;
|
||||
|
||||
if (!CreatePipe(&p->child_stdout_read, &p->child_stdout_write, &security_atrb, 0)) {
|
||||
Win32ProcessError(&process, command_line);
|
||||
return process;
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(p->child_stdout_read, HANDLE_FLAG_INHERIT, 0)) {
|
||||
Win32ProcessError(&process, command_line);
|
||||
return process;
|
||||
}
|
||||
|
||||
if (!CreatePipe(&p->child_stdin_read, &p->child_stdin_write, &security_atrb, 0)) {
|
||||
Win32ProcessError(&process, command_line);
|
||||
return process;
|
||||
}
|
||||
|
||||
if (!SetHandleInformation(p->child_stdin_write, HANDLE_FLAG_INHERIT, 0)) {
|
||||
Win32ProcessError(&process, command_line);
|
||||
return process;
|
||||
}
|
||||
|
||||
STARTUPINFOW startup = {};
|
||||
startup.cb = sizeof(STARTUPINFO);
|
||||
startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
|
||||
startup.hStdInput = p->child_stdin_read;
|
||||
startup.hStdOutput = p->child_stdout_write;
|
||||
startup.hStdError = p->child_stdout_write;
|
||||
startup.wShowWindow = SW_HIDE;
|
||||
|
||||
String16 cwd = ToString16(scratch, working_dir);
|
||||
String16 cmd = ToString16(scratch, command_line);
|
||||
void *env = NULL;
|
||||
|
||||
PROCESS_INFORMATION info = {};
|
||||
if (!CreateProcessW(L"c:\\windows\\system32\\cmd.exe", cmd.data, 0, 0, TRUE, 0, env, cwd.data, &startup, &info)) {
|
||||
Win32ProcessError(&process, command_line);
|
||||
return process;
|
||||
}
|
||||
|
||||
// Close handles to the stdin and stdout pipes no longer needed by the child process.
|
||||
// If they are not explicitly closed, there is no way to recognize that the child process has ended.
|
||||
CloseHandle(info.hThread);
|
||||
CloseHandle(p->child_stdin_read);
|
||||
CloseHandle(p->child_stdout_write);
|
||||
p->child_stdin_read = INVALID_HANDLE_VALUE;
|
||||
p->child_stdout_write = INVALID_HANDLE_VALUE;
|
||||
|
||||
p->handle = info.hProcess;
|
||||
process.is_valid = true;
|
||||
return process;
|
||||
}
|
||||
|
||||
bool WaitForExit(Process *process) {
|
||||
Assert(process->is_valid);
|
||||
Win32Process *p = (Win32Process *)process->platform;
|
||||
|
||||
if (WaitForSingleObject(p->handle, INFINITE) != WAIT_OBJECT_0) {
|
||||
Assert(!"Invalid codepath");
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD exit_code;
|
||||
bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0;
|
||||
if (get_exit_code_failed) {
|
||||
exit_code = -1;
|
||||
}
|
||||
|
||||
process->exit_code = (int)exit_code;
|
||||
Win32CloseProcess(process);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PollExitCode(Process *process) {
|
||||
Assert(process->is_valid);
|
||||
Win32Process *p = (Win32Process *)process->platform;
|
||||
|
||||
if (WaitForSingleObject(p->handle, 0) != WAIT_OBJECT_0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DWORD exit_code;
|
||||
bool get_exit_code_failed = GetExitCodeProcess(p->handle, &exit_code) == 0;
|
||||
if (get_exit_code_failed) {
|
||||
exit_code = -1;
|
||||
}
|
||||
|
||||
process->exit_code = (int)exit_code;
|
||||
Win32CloseProcess(process);
|
||||
return true;
|
||||
}
|
||||
|
||||
void KillProcess(Process *process) {
|
||||
Assert(process->is_valid);
|
||||
Win32Process *p = (Win32Process *)process->platform;
|
||||
|
||||
bool terminate_process_error = TerminateProcess(p->handle, -1) == 0;
|
||||
if (terminate_process_error) {
|
||||
Assert(0);
|
||||
}
|
||||
Win32CloseProcess(process);
|
||||
process->exit_code = -1;
|
||||
}
|
||||
|
||||
StdoutPollInfo PollStdout(Process *process, char *buffer, int64_t buffer_size) {
|
||||
Assert(process->is_valid);
|
||||
Win32Process *p = (Win32Process *)process->platform;
|
||||
|
||||
DWORD bytes_read = 0;
|
||||
DWORD bytes_avail = 0;
|
||||
|
||||
bool peek_error = PeekNamedPipe(p->child_stdout_read, buffer, (DWORD)buffer_size, &bytes_read, &bytes_avail, NULL) == 0;
|
||||
|
||||
StdoutPollInfo result = {bytes_read, bytes_avail};
|
||||
return result;
|
||||
}
|
||||
@@ -237,6 +237,15 @@ int main()
|
||||
SDL_free(sdl_config_path.data);
|
||||
}
|
||||
|
||||
// Process process = RunCmd("git log", WorkingDir);
|
||||
// Scratch scratch;
|
||||
// char *buffer = AllocArray(scratch, char, 4096);
|
||||
// for (int i = 0; i < 4; i += 1) {
|
||||
// StdoutPollInfo info = PollStdout(&process, buffer, 4096);
|
||||
// bool exited = PollExitCode(&process);
|
||||
// if (exited) break;
|
||||
// }
|
||||
|
||||
if (SDL_Init(SDL_INIT_VIDEO) == -1) {
|
||||
ReportErrorf("Couldn't initialize SDL! %s", SDL_GetError());
|
||||
return 1;
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
- search as a command to execute which is going to be in the title bar
|
||||
|
||||
- search backwards
|
||||
- draw indentation levels like in sublime (those lines) - we render chars one by one so seems relatively easy to figure out if whitespace belongs to beginning of line (make sure to add max value like 40 because of big files)
|
||||
- code sections, visual demarkation if beginning of line has a very specific text + goto next / goto prev section hotkey!
|
||||
- change size of command window because it's wacky
|
||||
- console should save history! Make all commands go through proper history saving route. History friendly append - save cursors, set cursor at end, restore cursors
|
||||
|
||||
- Search all buffers in 10X style, incrementally searched results popping up on every key press (maybe we need coroutine library in C so this is easier?)
|
||||
- Implement opening and polling processes, sending info back and forth
|
||||
- Open git commit using git --no-pager show <hash>
|
||||
- Implement shell interaction (here we also could use the coroutine library)
|
||||
- Search and replace
|
||||
|
||||
- word complete
|
||||
- Search all buffers in 10X style, incrementally searched results popping up on every key press (maybe we need coroutine library in C so this is easier?)
|
||||
- Search and replace
|
||||
- load in a next window
|
||||
- load in new window
|
||||
|
||||
@@ -21,11 +18,15 @@
|
||||
- drag and drop file into the window
|
||||
- exe icon
|
||||
|
||||
- how do we regen directory buffers?
|
||||
- load project command which loads files and config
|
||||
- load all files in a directory
|
||||
- global config and local config
|
||||
- open project from cmd
|
||||
|
||||
- draw indentation levels like in sublime (those lines) - we render chars one by one so seems relatively easy to figure out if whitespace belongs to beginning of line (make sure to add max value like 40 because of big files)
|
||||
- code sections, visual demarkation if beginning of line has a very specific text + goto next / goto prev section hotkey!
|
||||
|
||||
- page up and down should also scroll and leave you in exactly same scroll
|
||||
- I think the way sublime text and we display line highlights is confusing with multiple cursors (line highlight can be confused with selection)
|
||||
- ctrl + delete maybe should stop on new line but it keeps on going, sublime is much more careful with deleting
|
||||
|
||||
Reference in New Issue
Block a user