@@ -55,6 +55,16 @@ def generate(out_dir: Path) -> None:
5555 run_sh .chmod (0o755 )
5656 (out_dir / "run.bat" ).write_text (RUN_BAT )
5757
58+ # Covers this folder's own generated caches: lib/libprocessing_cpp.a
59+ # and include/Processing.h.gch. The .gch in particular is large
60+ # (100MB+) -- without this, anyone who runs `git add .` after their
61+ # first ./run.sh commits a 100MB+ binary straight into their repo
62+ # history. Doesn't cover .processing-cpp-build (the compiled sketch
63+ # binary), since that lands at the PROJECT root, one level above this
64+ # folder -- a .gitignore here can't reach it. The README tells the
65+ # user to add that line to their own project's .gitignore instead.
66+ (out_dir / ".gitignore" ).write_text (DRAGDROP_GITIGNORE )
67+
5868 # VS Code task. This can't be shipped directly as .vscode/tasks.json
5969 # inside this folder -- VS Code only reads .vscode/ at the workspace
6070 # ROOT, which is one level up (wherever the user's own main.cpp lives),
@@ -69,7 +79,7 @@ def generate(out_dir: Path) -> None:
6979 print (f"Generated drag-and-drop package: { out_dir } " )
7080 print (f" include/ ({ len (ENGINE_HEADERS )} headers), "
7181 f"src/ ({ len (ENGINE_SOURCES )} engine source files), "
72- f"README.md, run.sh, run.bat, vscode-task/, examples/, LICENSE" )
82+ f"README.md, run.sh, run.bat, .gitignore, vscode-task/, examples/, LICENSE" )
7383
7484
7585# =============================================================================
@@ -107,13 +117,32 @@ def generate(out_dir: Path) -> None:
107117
108118That's the entire workflow. `run.sh` finds your `.cpp` file, compiles it
109119against the engine, links it, and runs the result, in one step. The first
110- time you run it, it also compiles the engine itself, which takes about
111- 10-15 seconds; every run after that reuses the compiled engine and only
112- has to rebuild your own file, so it's fast.
120+ time you run it, it also compiles the engine itself and precompiles
121+ `Processing.h`, which together take about 15-20 seconds; every run after
122+ that reuses both of those and only has to compile your own file, so it's
123+ fast -- well under a second on most machines.
113124
114125On Windows, run `run.bat` the same way (from an MSYS2 shell, or by
115126double-clicking it).
116127
128+ ## If your project is a git repo
129+
130+ `run.sh` caches its build outputs in two places: `processing-cpp/lib/`
131+ and `processing-cpp/include/Processing.h.gch`. This folder already
132+ includes a `.gitignore` covering both of those, so they won't get
133+ committed if you run `git add processing-cpp`.
134+
135+ It can't cover `run.sh`'s other output, though: the compiled sketch
136+ itself, `.processing-cpp-build`, lands one level up, at the root of
137+ *your* project, not inside `processing-cpp/`. Add this line to your own
138+ project's `.gitignore`:
139+
140+ ```
141+ .processing-cpp-build*
142+ ```
143+
144+ (The trailing `*` catches `.processing-cpp-build.exe` on Windows too.)
145+
117146## Writing a sketch
118147
119148```cpp
@@ -186,6 +215,11 @@ def generate(out_dir: Path) -> None:
186215Makefile, editor build task, or something other than `run.sh` -- you don't
187216need to read this section to just use the library.
188217
218+ (`run.sh` itself does a little more than this -- it caches the compiled
219+ engine and a precompiled header for `Processing.h` so repeated runs are
220+ fast, as described above. Neither is required for a *working* build, only
221+ for a *fast* one; the command above is enough to get a sketch running.)
222+
189223## Installing GLFW, GLEW, and a compiler
190224
191225```sh
@@ -272,15 +306,54 @@ def generate(out_dir: Path) -> None:
272306# ./processing-cpp/run.sh # auto-detects .cpp files next to this folder
273307# ./processing-cpp/run.sh main.cpp app.cpp # explicit, e.g. a multi-file sketch
274308#
275- # The engine itself is compiled once and cached in processing-cpp/lib/ --
276- # only your own file(s) get recompiled on later runs, unless the engine
277- # source in this folder changes (e.g. you updated the package).
309+ # Two things get cached here so repeated runs stay fast, both rebuilt
310+ # automatically only when their inputs change:
311+ # - the engine itself, compiled once into processing-cpp/lib/
312+ # - a precompiled header for Processing.h, written next to it as
313+ # processing-cpp/include/Processing.h.gch (g++ only looks for a .gch
314+ # file in the SAME directory as the header it precompiles -- it
315+ # can't live in lib/ alongside the engine, even though that would
316+ # read more naturally)
317+ # Neither cache is required for correctness -- delete processing-cpp/lib/
318+ # and Processing.h.gch and the next run just rebuilds both from scratch.
278319set -e
279320
280321SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
281322PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
282323cd "$PROJECT_DIR"
283324
325+ if ! command -v g++ >/dev/null 2>&1; then
326+ echo "error: g++ not found on PATH." >&2
327+ echo "Install a C++ compiler, then try again -- see the \\ "Dependencies\\ " section of processing-cpp/README.md." >&2
328+ exit 1
329+ fi
330+
331+ # Wraps a build command so a failure gets a one-line, specific cause
332+ # instead of just whatever raw error g++/ld printed. Every g++/ar call in
333+ # this script goes through here -- the engine build is the first thing
334+ # that touches GLFW/GLEW (Processing.h includes both directly), so a
335+ # missing-dependency failure can show up there, in the PCH build, or in
336+ # the final compile, depending on what is/isn't already cached.
337+ run_build_step() {
338+ local description="$1"
339+ shift
340+ local output
341+ if ! output="$("$@" 2>&1)"; then
342+ echo "$output" >&2
343+ echo "" >&2
344+ if echo "$output" | grep -qE "GLFW/glfw3\\ .h|GL/glew\\ .h"; then
345+ echo "error: $description failed -- GLFW or GLEW headers not found." >&2
346+ echo "See the \\ "Dependencies\\ " section of processing-cpp/README.md to install them." >&2
347+ elif echo "$output" | grep -qE "cannot find -lglfw|cannot find -lGLEW"; then
348+ echo "error: $description failed -- GLFW or GLEW library not found at link time." >&2
349+ echo "See the \\ "Dependencies\\ " section of processing-cpp/README.md to install them." >&2
350+ else
351+ echo "error: $description failed. See the output above." >&2
352+ fi
353+ exit 1
354+ fi
355+ }
356+
284357if [ "$#" -gt 0 ]; then
285358 SOURCES=("$@")
286359else
@@ -303,19 +376,39 @@ def generate(out_dir: Path) -> None:
303376case "$OS" in
304377 Darwin)
305378 GL_LIBS=(-lglfw -lGLEW -framework OpenGL -framework Cocoa -framework IOKit -framework CoreVideo)
379+ COMPILE_FLAGS=()
306380 ;;
307381 Linux)
308382 GL_LIBS=(-lglfw -lGLEW -lGL -lGLU -lm -pthread)
383+ # -pthread isn't just a link flag -- it also defines _REENTRANT
384+ # during compilation. The PCH build below has to see the same
385+ # define, or g++ rejects the cached .gch as stale and silently
386+ # re-parses Processing.h from source every time (caught by
387+ # actually building the package and checking with -Winvalid-pch
388+ # -- it doesn't show up as a build failure, just quietly stops
389+ # being fast).
390+ COMPILE_FLAGS=(-pthread)
309391 ;;
310392 *)
311393 echo "error: unrecognized OS '$OS' -- on Windows, use run.bat instead" >&2
312394 exit 1
313395 ;;
314396esac
315397
398+ # DEFINES must be IDENTICAL between the PCH build below and the final
399+ # sketch compile further down, and so must COMPILE_FLAGS above -- g++
400+ # only uses a .gch if every relevant flag matches the compile it's being
401+ # considered for. A mismatch doesn't break the build, it just silently
402+ # falls back to parsing Processing.h from source (with a -Winvalid-pch
403+ # warning if you pass that flag to check), so the caching would quietly
404+ # stop helping. Defined once here so the two invocations can't drift
405+ # apart from each other.
406+ DEFINES=(-DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE)
407+
316408ENGINE_DIR="$SCRIPT_DIR"
317409LIB_DIR="$ENGINE_DIR/lib"
318410LIB_A="$LIB_DIR/libprocessing_cpp.a"
411+ PCH_FILE="$ENGINE_DIR/include/Processing.h.gch"
319412mkdir -p "$LIB_DIR"
320413
321414NEED_ENGINE_BUILD=0
@@ -331,20 +424,36 @@ def generate(out_dir: Path) -> None:
331424
332425if [ "$NEED_ENGINE_BUILD" -eq 1 ]; then
333426 echo "Compiling engine (first run, or engine source changed; ~10-15s)..."
334- g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" \\
335- -DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE \\
336- "$ENGINE_DIR/src/Processing.cpp" -o "$LIB_DIR/Processing.o"
337- g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" \\
338- -DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE \\
339- "$ENGINE_DIR/src/Processing_defaults.cpp" -o "$LIB_DIR/Processing_defaults.o"
340- ar rcs "$LIB_A" "$LIB_DIR/Processing.o" "$LIB_DIR/Processing_defaults.o"
427+ run_build_step "engine compile" \\
428+ g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" "${DEFINES[@]}" \\
429+ "$ENGINE_DIR/src/Processing.cpp" -o "$LIB_DIR/Processing.o"
430+ run_build_step "engine compile" \\
431+ g++ -std=c++17 -O2 -c -I"$ENGINE_DIR/include" "${DEFINES[@]}" \\
432+ "$ENGINE_DIR/src/Processing_defaults.cpp" -o "$LIB_DIR/Processing_defaults.o"
433+ run_build_step "engine archive" \\
434+ ar rcs "$LIB_A" "$LIB_DIR/Processing.o" "$LIB_DIR/Processing_defaults.o"
341435 rm -f "$LIB_DIR/Processing.o" "$LIB_DIR/Processing_defaults.o"
342436fi
343437
438+ NEED_PCH_BUILD=0
439+ if [ ! -f "$PCH_FILE" ]; then
440+ NEED_PCH_BUILD=1
441+ elif [ "$ENGINE_DIR/include/Processing.h" -nt "$PCH_FILE" ]; then
442+ NEED_PCH_BUILD=1
443+ fi
444+
445+ if [ "$NEED_PCH_BUILD" -eq 1 ]; then
446+ echo "Precompiling Processing.h (first run, or it changed; speeds up every build after this one)..."
447+ run_build_step "header precompile" \\
448+ g++ -std=c++17 -I"$ENGINE_DIR/include" "${DEFINES[@]}" "${COMPILE_FLAGS[@]}" \\
449+ -x c++-header "$ENGINE_DIR/include/Processing.h" -o "$PCH_FILE"
450+ fi
451+
344452OUT="$PROJECT_DIR/.processing-cpp-build"
345- g++ -std=c++17 -I"$ENGINE_DIR/include" "${SOURCES[@]}" \\
346- -L"$LIB_DIR" -lprocessing_cpp "${GL_LIBS[@]}" \\
347- -o "$OUT"
453+ run_build_step "sketch compile" \\
454+ g++ -std=c++17 -I"$ENGINE_DIR/include" "${DEFINES[@]}" "${SOURCES[@]}" \\
455+ -L"$LIB_DIR" -lprocessing_cpp "${GL_LIBS[@]}" \\
456+ -o "$OUT"
348457
349458echo "Running..."
350459exec "$OUT"
@@ -360,6 +469,14 @@ def generate(out_dir: Path) -> None:
360469REM Usage:
361470REM processing-cpp\\ run.bat
362471REM processing-cpp\\ run.bat main.cpp app.cpp
472+ REM
473+ REM Caches two things so repeated runs stay fast: the engine itself
474+ REM (processing-cpp\\ lib\\ libprocessing_cpp.a) and a precompiled header
475+ REM for Processing.h (processing-cpp\\ include\\ Processing.h.gch -- g++
476+ REM only looks for a .gch file next to the header it precompiles, so it
477+ REM has to live in include\\ , not next to the engine in lib\\ ). Neither
478+ REM is required for correctness; delete both and the next run rebuilds
479+ REM them from scratch.
363480setlocal enabledelayedexpansion
364481
365482set "SCRIPT_DIR=%~dp0"
@@ -380,31 +497,62 @@ def generate(out_dir: Path) -> None:
380497
381498echo Building: %SOURCES%
382499
500+ REM Must match the DEFINES used for the PCH build exactly, or g++ will
501+ REM reject the cached .gch as stale and silently fall back to parsing
502+ REM Processing.h from source every time (with a -Winvalid-pch warning).
503+ set "DEFINES=-DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE -D_USE_MATH_DEFINES"
504+
383505set "ENGINE_DIR=%SCRIPT_DIR%"
384506set "LIB_DIR=%ENGINE_DIR%lib"
385507set "LIB_A=%LIB_DIR%\\ libprocessing_cpp.a"
508+ set "PCH_FILE=%ENGINE_DIR%include\\ Processing.h.gch"
386509if not exist "%LIB_DIR%" mkdir "%LIB_DIR%"
387510
388511set NEED_ENGINE_BUILD=0
389512if not exist "%LIB_A%" set NEED_ENGINE_BUILD=1
390513
391514if %NEED_ENGINE_BUILD%==1 (
392515 echo Compiling engine ^(first run; ~10-15s^)...
393- g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" -DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE -D_USE_MATH_DEFINES "%ENGINE_DIR%src\\ Processing.cpp" -o "%LIB_DIR%\\ Processing.o"
516+ g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" %DEFINES% "%ENGINE_DIR%src\\ Processing.cpp" -o "%LIB_DIR%\\ Processing.o"
394517 if errorlevel 1 exit /b 1
395- g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" -DPROCESSING_HAS_STB_IMAGE -DPROCESSING_HAS_STB_TRUETYPE -D_USE_MATH_DEFINES "%ENGINE_DIR%src\\ Processing_defaults.cpp" -o "%LIB_DIR%\\ Processing_defaults.o"
518+ g++ -std=c++17 -O2 -c -I"%ENGINE_DIR%include" %DEFINES% "%ENGINE_DIR%src\\ Processing_defaults.cpp" -o "%LIB_DIR%\\ Processing_defaults.o"
396519 if errorlevel 1 exit /b 1
397520 ar rcs "%LIB_A%" "%LIB_DIR%\\ Processing.o" "%LIB_DIR%\\ Processing_defaults.o"
398521 del "%LIB_DIR%\\ Processing.o" "%LIB_DIR%\\ Processing_defaults.o"
399522)
400523
401- g++ -std=c++17 -I"%ENGINE_DIR%include" %SOURCES% -L"%LIB_DIR%" -lprocessing_cpp -lglfw3 -lglew32 -lopengl32 -lglu32 -lcomdlg32 -lshell32 -lole32 -luuid -mwindows -pthread -D_USE_MATH_DEFINES -o "%PROJECT_DIR%\\ .processing-cpp-build.exe"
524+ set NEED_PCH_BUILD=0
525+ if not exist "%PCH_FILE%" set NEED_PCH_BUILD=1
526+
527+ if %NEED_PCH_BUILD%==1 (
528+ echo Precompiling Processing.h ^(first run; speeds up every build after this one^)...
529+ REM -pthread has to be here too, matching the final compile below --
530+ REM it defines _REENTRANT during compilation, not just at link time,
531+ REM so without it here g++ rejects this .gch as stale on every build
532+ REM and silently re-parses Processing.h from source instead (caught
533+ REM by actually building this package and checking with the
534+ REM -Winvalid-pch warning flag -- it's not a build failure, the
535+ REM caching just quietly stops working).
536+ g++ -std=c++17 -I"%ENGINE_DIR%include" %DEFINES% -pthread -x c++-header "%ENGINE_DIR%include\\ Processing.h" -o "%PCH_FILE%"
537+ if errorlevel 1 exit /b 1
538+ )
539+
540+ g++ -std=c++17 -I"%ENGINE_DIR%include" %DEFINES% %SOURCES% -L"%LIB_DIR%" -lprocessing_cpp -lglfw3 -lglew32 -lopengl32 -lglu32 -lcomdlg32 -lshell32 -lole32 -luuid -mwindows -pthread -o "%PROJECT_DIR%\\ .processing-cpp-build.exe"
402541if errorlevel 1 exit /b 1
403542
404543echo Running...
405544"%PROJECT_DIR%\\ .processing-cpp-build.exe"
406545'''
407546
547+ DRAGDROP_GITIGNORE = '''\
548+ # Generated by run.sh / run.bat -- safe to delete, will be rebuilt
549+ # automatically on the next run. Not meant to be committed: the engine
550+ # archive is rebuilt in seconds, and the precompiled header in
551+ # particular is 100MB+, which is a bad thing to put in git history.
552+ lib/
553+ include/Processing.h.gch
554+ '''
555+
408556VSCODE_TASKS_JSON = '''\
409557 {
410558 // Lets you build-and-run with Ctrl+Shift+B (Cmd+Shift+B on macOS)
0 commit comments