basalt-win32 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -55,6 +55,14 @@
55
55
  #ifdef BASALT_HAS_SKIA
56
56
  #include "Win32SkiaModule.h"
57
57
  #endif
58
+ #if BASALT_HAS_SKIA_GRAPHITE
59
+ // The 3.x module, from basalt-skia. Registered here rather than through
60
+ // BASALT_PACKAGE_TURBO_MODULES because that property has core generate the
61
+ // call, and core is shared with hosts that cannot link this Skia -- see that
62
+ // package's CMakeLists. The same arrangement the 2.x module above has, and
63
+ // the same one main_appkit.mm makes.
64
+ #include "BasaltSkiaModule.h"
65
+ #endif
58
66
  #include "Win32Packaging.h"
59
67
  #include "PointerButtons.h"
60
68
  #include "WindowControl.h"
@@ -70,6 +78,7 @@
70
78
  #include "TextSelection.h"
71
79
  #include "Win32TouchDispatcher.h"
72
80
  #include "Win32UiThread.h"
81
+ #include "Win32UiTimer.h"
73
82
  #include "DragAndDrop.h"
74
83
  #include "Win32DropTarget.h"
75
84
  #include "Win32TitleBar.h"
@@ -162,16 +171,6 @@ constexpr int kInitialHeight = 700;
162
171
  // See e2e/demo.js. Only used when no module name is given.
163
172
  constexpr const char *kRenderFunctionName = "basaltRender";
164
173
 
165
- constexpr UINT_PTR kSecondTreeTimer = 100;
166
-
167
- // BASALT_QUIT_AFTER_MS's timer. A constant rather than the literal 101 it used
168
- // to be written as at its one call site: the spinner timer below was given 101
169
- // too, and `SetTimer` with an id that is already in use *replaces* the timer
170
- // rather than failing. An app with an <ActivityIndicator> in it therefore
171
- // silently cancelled its own shutdown, and the only symptom was a host that
172
- // never exited.
173
- constexpr UINT_PTR kQuitAfterTimer = 101;
174
-
175
174
  // One window, and the surface in it.
176
175
  //
177
176
  // A window is a surface: a surface is what has a size, a layout context and a
@@ -393,12 +392,16 @@ void requestRepaint() {
393
392
  // Win32AnimationChoreographer.h gives about its own timer: a timer that runs
394
393
  // whether or not anything is animating wakes the process sixty times a second
395
394
  // forever, which matters more on a laptop than the frame interval does.
396
- constexpr UINT_PTR kAnimationTimer = 102;
397
-
398
- // BASALT_TEST_QUIT_FILE's poll. Repeating, unlike kQuitAfterTimer's one shot:
399
- // it looks for the file until it finds it. See core/TestQuitFile.h.
400
- constexpr UINT_PTR kQuitFileTimer = 103;
401
- bool gAnimationTimerRunning = false;
395
+ //
396
+ // Not `SetTimer`, and this is the timer that proves why: the one thing it
397
+ // exists to do is repaint, and a `WM_TIMER` is the message Windows withholds
398
+ // while there is painting to do. A spinner that stops the moment the window
399
+ // gets busy is the exact failure. See win32/Win32UiTimer.h.
400
+ basalt::UiTimerId gAnimationTimer = 0;
401
+
402
+ // Defined below, and named here because the timer that calls it is started
403
+ // above it.
404
+ void tickAnimations(HWND hwnd);
402
405
  // When the last tick ran, so a frame delay is measured rather than assumed.
403
406
  // A GIF's delays are in hundredths of a second and the timer is 16ms, so
404
407
  // counting ticks would quantise every delay to a multiple of the tick and a
@@ -413,15 +416,16 @@ void updateAnimationTimer() {
413
416
  gHost.main().root->hasAnimatedImage() ||
414
417
  (gHost.logBoxRoot != nullptr &&
415
418
  (gHost.logBoxRoot->hasAnimatingSpinner() || gHost.logBoxRoot->hasAnimatedImage()));
416
- if (wanted == gAnimationTimerRunning) {
419
+ if (wanted == (gAnimationTimer != 0)) {
417
420
  return;
418
421
  }
419
- gAnimationTimerRunning = wanted;
420
422
  if (wanted) {
421
423
  gLastAnimationTick = std::chrono::steady_clock::now();
422
- SetTimer(gHost.main().window, kAnimationTimer, 16, nullptr);
424
+ HWND window = gHost.main().window;
425
+ gAnimationTimer = basalt::scheduleUiTimer(16, true, [window] { tickAnimations(window); });
423
426
  } else {
424
- KillTimer(gHost.main().window, kAnimationTimer);
427
+ basalt::cancelUiTimer(gAnimationTimer);
428
+ gAnimationTimer = 0;
425
429
  }
426
430
  }
427
431
 
@@ -566,6 +570,22 @@ facebook::react::TurboModuleProviders makeTurboModuleProviders(
566
570
  if (name == basalt::Win32SkiaModule::kModuleName) {
567
571
  return std::make_shared<basalt::Win32SkiaModule>(jsInvoker);
568
572
  }
573
+ #endif
574
+ #if BASALT_HAS_SKIA_GRAPHITE
575
+ // And 3.x, which is Graphite over Dawn and a different module under
576
+ // the same name. Never both: cmake/Skia.cmake refuses a 3.x install by
577
+ // version, so BASALT_HAS_SKIA is defined in exactly the builds this is
578
+ // not.
579
+ //
580
+ // Without this the canvas is built, linked and unreachable: the view
581
+ // mounts, basalt-skia's C++ is all in the binary, and the app still
582
+ // ends at getEnforcing('RNSkiaModule') on its first Skia import --
583
+ // which is where kino's first Windows run stopped.
584
+ if (name == basalt::BasaltSkiaModule::kModuleName) {
585
+ if (auto module = basalt::makeSkiaTurboModule(name, jsInvoker)) {
586
+ return module;
587
+ }
588
+ }
569
589
  #endif
570
590
  if (name == basalt::DesktopBlobModule::kModuleName) {
571
591
  return std::make_shared<basalt::DesktopBlobModule>(jsInvoker);
@@ -898,25 +918,18 @@ struct ScriptedInput {
898
918
  // Fired from timers keyed by index, so the vector has to outlive the loop.
899
919
  std::vector<ScriptedInput> gScriptedInput;
900
920
 
901
- constexpr UINT_PTR kScriptedInputTimerBase = 200;
902
-
903
- // Every timer id this host uses is distinct.
921
+ // There is no table of timer ids here any more, and that is the point.
904
922
  //
905
- // `SetTimer` with an id that is already in use replaces the timer rather than
906
- // failing, so a collision is silent and its symptom is whatever the timer it
907
- // replaced was for. That happened: the spinner's repaint timer was given the
908
- // same id as BASALT_QUIT_AFTER_MS's, and an app with an <ActivityIndicator> in
909
- // it cancelled its own shutdown and hung. A static_assert cannot check the
910
- // scripted range against the rest, so the base is held above everything else.
911
- static_assert(kSecondTreeTimer != kQuitAfterTimer && kSecondTreeTimer != kAnimationTimer &&
912
- kSecondTreeTimer != kQuitFileTimer && kQuitAfterTimer != kAnimationTimer &&
913
- kQuitAfterTimer != kQuitFileTimer && kAnimationTimer != kQuitFileTimer,
914
- "two timers share an id, and SetTimer would silently replace one with the other");
915
- static_assert(kScriptedInputTimerBase > kSecondTreeTimer &&
916
- kScriptedInputTimerBase > kQuitAfterTimer &&
917
- kScriptedInputTimerBase > kAnimationTimer &&
918
- kScriptedInputTimerBase > kQuitFileTimer,
919
- "the scripted-input ids count up from the base and must start above the rest");
923
+ // Every one of this host's waits used to be a `SetTimer` id written by hand,
924
+ // and `SetTimer` with an id already in use *replaces* the timer rather than
925
+ // failing -- so a collision was silent and showed up as whatever the replaced
926
+ // timer had been for. It happened: the spinner's repaint timer was given the
927
+ // same id as BASALT_QUIT_AFTER_MS's, and an app with an <ActivityIndicator>
928
+ // in it cancelled its own shutdown and hung. Two static_asserts guarded
929
+ // against the next one.
930
+ //
931
+ // win32/Win32UiTimer.h hands out its own ids from a counter, so there is
932
+ // nothing left to collide and nothing left to assert about.
920
933
 
921
934
  // The numbers in "10,20,30" -- however many there are, which is what lets one
922
935
  // parser read both a tap's pair and a drag's four.
@@ -938,9 +951,10 @@ std::vector<double> parseNumbers(const std::string &spec) {
938
951
  return numbers;
939
952
  }
940
953
 
941
- void CALLBACK fireScriptedInput(HWND hwnd, UINT, UINT_PTR id, DWORD) {
942
- KillTimer(hwnd, id);
943
- const size_t index = static_cast<size_t>(id - kScriptedInputTimerBase);
954
+ // One scheduled action, on the UI thread. An index and a window rather than a
955
+ // TIMERPROC's four arguments: the timer behind this carries a closure, so
956
+ // there is nothing to recover from an id.
957
+ void fireScriptedInput(HWND hwnd, size_t index) {
944
958
  if (index >= gScriptedInput.size()) {
945
959
  return;
946
960
  }
@@ -1159,10 +1173,13 @@ void CALLBACK fireScriptedInput(HWND hwnd, UINT, UINT_PTR id, DWORD) {
1159
1173
  // the last one, so a drag can be scheduled behind the taps.
1160
1174
  UINT scheduleScriptedInput(const ScriptedInput &action, UINT delayMs) {
1161
1175
  gScriptedInput.push_back(action);
1162
- SetTimer(gHost.main().window,
1163
- kScriptedInputTimerBase + gScriptedInput.size() - 1,
1164
- delayMs,
1165
- fireScriptedInput);
1176
+ const size_t index = gScriptedInput.size() - 1;
1177
+ HWND window = gHost.main().window;
1178
+ // Not `SetTimer`: scripted input exists to drive a *running* app, and the
1179
+ // apps worth driving are the ones that paint -- which is exactly when a
1180
+ // WM_TIMER never arrives. Every instrument in this file was silently
1181
+ // inoperative on an app with a `<Canvas>`. See win32/Win32UiTimer.h.
1182
+ basalt::scheduleUiTimer(delayMs, false, [window, index] { fireScriptedInput(window, index); });
1166
1183
  return delayMs + 1000;
1167
1184
  }
1168
1185
 
@@ -1804,17 +1821,9 @@ LRESULT CALLBACK hostProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
1804
1821
  }
1805
1822
  return 0;
1806
1823
 
1807
- case WM_TIMER:
1808
- if (wparam == kAnimationTimer) {
1809
- tickAnimations(hwnd);
1810
- return 0;
1811
- }
1812
- if (wparam == kSecondTreeTimer) {
1813
- KillTimer(hwnd, kSecondTreeTimer);
1814
- std::fprintf(stderr, "--- committing tree 2 from JS ---\n");
1815
- callRenderFunction(2);
1816
- }
1817
- return 0;
1824
+ // No WM_TIMER case. Nothing this host schedules arrives that way any
1825
+ // more -- see win32/Win32UiTimer.h for why it could not be relied on --
1826
+ // and swallowing the message would hide a timer somebody else set.
1818
1827
 
1819
1828
  case WM_PAINT: {
1820
1829
  PAINTSTRUCT paint{};
@@ -2529,7 +2538,10 @@ int main(int argc, char **argv) {
2529
2538
  if (gHost.moduleName.empty()) {
2530
2539
  std::fprintf(stderr, "--- committing tree 1 from JS ---\n");
2531
2540
  callRenderFunction(1);
2532
- SetTimer(gHost.main().window, kSecondTreeTimer, 2000, nullptr);
2541
+ basalt::scheduleUiTimer(2000, false, [] {
2542
+ std::fprintf(stderr, "--- committing tree 2 from JS ---\n");
2543
+ callRenderFunction(2);
2544
+ });
2533
2545
  }
2534
2546
 
2535
2547
  UINT scriptedDelayMs = 1500;
@@ -2643,21 +2655,20 @@ int main(int argc, char **argv) {
2643
2655
  // clicking anything. The same escape hatch both other hosts have.
2644
2656
  if (const char *quitAfter = std::getenv("BASALT_QUIT_AFTER_MS")) {
2645
2657
  const UINT delay = static_cast<UINT>(std::strtoul(quitAfter, nullptr, 10));
2646
- SetTimer(
2647
- gHost.main().window, kQuitAfterTimer, delay, [](HWND hwnd, UINT, UINT_PTR id, DWORD) {
2648
- KillTimer(hwnd, id);
2649
- // The same line the other two hosts log, so a person reading three
2650
- // transcripts side by side sees the same shutdown in each. This
2651
- // host was silent about it, which is how a scenario came to assert
2652
- // on a string only two of them print.
2653
- std::fprintf(stderr, "BASALT_QUIT_AFTER_MS elapsed; quitting\n");
2654
- // What WM_CLOSE does, without going through WM_CLOSE. This is the
2655
- // harness ending the run, not a person closing a window, and an app
2656
- // that intercepts its own close would otherwise refuse it -- a test
2657
- // that asks "does refusing work" by never exiting.
2658
- captureBeforeTeardown();
2659
- DestroyWindow(hwnd);
2660
- });
2658
+ HWND window = gHost.main().window;
2659
+ basalt::scheduleUiTimer(delay, false, [window] {
2660
+ // The same line the other two hosts log, so a person reading three
2661
+ // transcripts side by side sees the same shutdown in each. This
2662
+ // host was silent about it, which is how a scenario came to assert
2663
+ // on a string only two of them print.
2664
+ std::fprintf(stderr, "BASALT_QUIT_AFTER_MS elapsed; quitting\n");
2665
+ // What WM_CLOSE does, without going through WM_CLOSE. This is the
2666
+ // harness ending the run, not a person closing a window, and an app
2667
+ // that intercepts its own close would otherwise refuse it -- a test
2668
+ // that asks "does refusing work" by never exiting.
2669
+ captureBeforeTeardown();
2670
+ DestroyWindow(window);
2671
+ });
2661
2672
  }
2662
2673
 
2663
2674
  // BASALT_TEST_QUIT_FILE: the same shutdown, at a moment the harness picks
@@ -2671,26 +2682,25 @@ int main(int argc, char **argv) {
2671
2682
  //
2672
2683
  // Two instruments, one timer, because they want the same thing at different
2673
2684
  // moments: the quit file is the harness deciding, BASALT_QUIT_WHEN_SETTLED is
2674
- // the app noticing it has nothing left to do. One timer is also one id, and
2675
- // SetTimer replaces silently on a collision. See core/TestSettle.h.
2685
+ // the app noticing it has nothing left to do. See core/TestSettle.h.
2676
2686
  if (basalt::testQuitFilePath().has_value() || basalt::quitWhenSettledMs().has_value()) {
2677
- SetTimer(gHost.main().window,
2678
- kQuitFileTimer,
2679
- basalt::kTestQuitFilePollMs,
2680
- [](HWND hwnd, UINT, UINT_PTR id, DWORD) {
2681
- const char *why = nullptr;
2682
- if (basalt::testQuitFileAppeared()) {
2683
- why = basalt::kTestQuitFileMessage;
2684
- } else if (basalt::hasSettled()) {
2685
- why = basalt::kQuitWhenSettledMessage;
2686
- } else {
2687
- return;
2688
- }
2689
- KillTimer(hwnd, id);
2690
- std::fprintf(stderr, "%s\n", why);
2691
- captureBeforeTeardown();
2692
- DestroyWindow(hwnd);
2693
- });
2687
+ HWND window = gHost.main().window;
2688
+ static basalt::UiTimerId poll = 0;
2689
+ poll = basalt::scheduleUiTimer(basalt::kTestQuitFilePollMs, true, [window] {
2690
+ const char *why = nullptr;
2691
+ if (basalt::testQuitFileAppeared()) {
2692
+ why = basalt::kTestQuitFileMessage;
2693
+ } else if (basalt::hasSettled()) {
2694
+ why = basalt::kQuitWhenSettledMessage;
2695
+ } else {
2696
+ return;
2697
+ }
2698
+ basalt::cancelUiTimer(poll);
2699
+ poll = 0;
2700
+ std::fprintf(stderr, "%s\n", why);
2701
+ captureBeforeTeardown();
2702
+ DestroyWindow(window);
2703
+ });
2694
2704
  }
2695
2705
 
2696
2706
  // Not a plain GetMessage loop: the event beat has to be induced each time the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-win32",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "The Windows view layer for basalt-core: a Win32 and Direct2D view layer, a Fabric mounting manager, and a host. Everything behind them is in basalt-core.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -22,6 +22,6 @@
22
22
  "react-native": "*"
23
23
  },
24
24
  "dependencies": {
25
- "basalt-core": "0.2.0"
25
+ "basalt-core": "0.2.2"
26
26
  }
27
27
  }