farce 0 → 0.1.0

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.
Files changed (420) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/CODE_OF_CONDUCT.md +26 -0
  4. data/CONTRIBUTING.md +71 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +1525 -0
  7. data/SECURITY.md +10 -0
  8. data/docs/benchmarks.md +185 -0
  9. data/docs/gems/dry-types.md +290 -0
  10. data/docs/gems/msgpack.md +70 -0
  11. data/docs/gems/ractor-shim.md +63 -0
  12. data/docs/modes.md +628 -0
  13. data/docs/scopes.md +649 -0
  14. data/docs/variants.md +346 -0
  15. data/ext/ext_helper.rb +20 -0
  16. data/ext/farce/README.md +21 -0
  17. data/ext/farce/atom.c +1023 -0
  18. data/ext/farce/bounded_map.c +1683 -0
  19. data/ext/farce/containers.h +77 -0
  20. data/ext/farce/counter.c +399 -0
  21. data/ext/farce/darwin.c +100 -0
  22. data/ext/farce/depend +12 -0
  23. data/ext/farce/dict.c +1523 -0
  24. data/ext/farce/dict.h +152 -0
  25. data/ext/farce/drivers.c +237 -0
  26. data/ext/farce/exchanger.c +299 -0
  27. data/ext/farce/extconf.rb +99 -0
  28. data/ext/farce/farce.c +359 -0
  29. data/ext/farce/flag.c +288 -0
  30. data/ext/farce/io.c +338 -0
  31. data/ext/farce/lock.c +510 -0
  32. data/ext/farce/map.c +2240 -0
  33. data/ext/farce/priority_queue.c +2056 -0
  34. data/ext/farce/queue.c +1059 -0
  35. data/ext/farce/reactor.c +820 -0
  36. data/ext/farce/reactor.h +103 -0
  37. data/ext/farce/shareable.h +31 -0
  38. data/ext/farce/signal.c +350 -0
  39. data/ext/farce/transaction.c +354 -0
  40. data/ext/farce/transaction.h +40 -0
  41. data/ext/farce/tree_map.c +1953 -0
  42. data/ext/farce/trie.c +2020 -0
  43. data/ext/farce/unshareable.c +155 -0
  44. data/ext/farce/unshared_io_pool.h +234 -0
  45. data/ext/farce/unshared_signal.c +263 -0
  46. data/ext/farce/unshared_wait.h +193 -0
  47. data/ext/farce/unsupported.c +6 -0
  48. data/ext/farce/vector.c +1195 -0
  49. data/ext/farce/weak_map.c +1714 -0
  50. data/ext/java/org/farce/BoundedMap.java +394 -0
  51. data/ext/java/org/farce/FiberScheduler.java +141 -0
  52. data/ext/java/org/farce/PriorityKey.java +45 -0
  53. data/ext/java/org/farce/PriorityQueue.java +373 -0
  54. data/ext/java/org/farce/QueueSignal.java +28 -0
  55. data/ext/rebind/README.md +14 -0
  56. data/ext/rebind/extconf.rb +10 -0
  57. data/ext/rebind/rebind.c +186 -0
  58. data/lib/farce/_yard/internal.rb +13 -0
  59. data/lib/farce/_yard/macros.rb +61 -0
  60. data/lib/farce/_yard/ractor.rb +46 -0
  61. data/lib/farce/abstract/atom.rb +186 -0
  62. data/lib/farce/abstract/bounded_map.rb +232 -0
  63. data/lib/farce/abstract/collection.rb +151 -0
  64. data/lib/farce/abstract/concurrent_map.rb +381 -0
  65. data/lib/farce/abstract/counter.rb +193 -0
  66. data/lib/farce/abstract/duplicable_map.rb +229 -0
  67. data/lib/farce/abstract/exchanger.rb +26 -0
  68. data/lib/farce/abstract/flag.rb +71 -0
  69. data/lib/farce/abstract/lazy.rb +115 -0
  70. data/lib/farce/abstract/lease.rb +104 -0
  71. data/lib/farce/abstract/lease_map.rb +261 -0
  72. data/lib/farce/abstract/lease_pool.rb +91 -0
  73. data/lib/farce/abstract/lfu_map.rb +20 -0
  74. data/lib/farce/abstract/lru_map.rb +25 -0
  75. data/lib/farce/abstract/map.rb +345 -0
  76. data/lib/farce/abstract/molecule.rb +245 -0
  77. data/lib/farce/abstract/port.rb +74 -0
  78. data/lib/farce/abstract/priority_queue.rb +117 -0
  79. data/lib/farce/abstract/queue.rb +269 -0
  80. data/lib/farce/abstract/scheduler.rb +111 -0
  81. data/lib/farce/abstract/set.rb +910 -0
  82. data/lib/farce/abstract/sorted_set.rb +172 -0
  83. data/lib/farce/abstract/timer_queue.rb +136 -0
  84. data/lib/farce/abstract/tree_map.rb +269 -0
  85. data/lib/farce/abstract/value.rb +68 -0
  86. data/lib/farce/abstract/vector.rb +1126 -0
  87. data/lib/farce/abstract/weak_atom.rb +32 -0
  88. data/lib/farce/abstract/weak_key_map.rb +12 -0
  89. data/lib/farce/abstract/weak_map.rb +13 -0
  90. data/lib/farce/abstract/weak_set.rb +12 -0
  91. data/lib/farce/abstract/weak_value_map.rb +12 -0
  92. data/lib/farce/abstract.rb +17 -0
  93. data/lib/farce/atom.rb +293 -0
  94. data/lib/farce/class_mirror.rb +87 -0
  95. data/lib/farce/clock.rb +121 -0
  96. data/lib/farce/config.rb +229 -0
  97. data/lib/farce/counter.rb +71 -0
  98. data/lib/farce/deduper.rb +122 -0
  99. data/lib/farce/engine/jruby/bounded_map.rb +314 -0
  100. data/lib/farce/engine/jruby/fiber_scheduler.jar +0 -0
  101. data/lib/farce/engine/jruby/fiber_scheduler.rb +119 -0
  102. data/lib/farce/engine/jruby/lease_waiting.rb +19 -0
  103. data/lib/farce/engine/jruby/map.rb +505 -0
  104. data/lib/farce/engine/jruby/mutable_numeric_copy.rb +42 -0
  105. data/lib/farce/engine/jruby/signal.rb +147 -0
  106. data/lib/farce/engine/jruby.rb +63 -0
  107. data/lib/farce/engine/jvm/concurrent_weak_registry.rb +55 -0
  108. data/lib/farce/engine/jvm/counter.rb +102 -0
  109. data/lib/farce/engine/jvm/extension.rb +32 -0
  110. data/lib/farce/engine/jvm/farce.jar +0 -0
  111. data/lib/farce/engine/jvm/flag.rb +79 -0
  112. data/lib/farce/engine/jvm/priority_queue.rb +217 -0
  113. data/lib/farce/engine/jvm/tree_map.rb +350 -0
  114. data/lib/farce/engine/jvm/types.rb +180 -0
  115. data/lib/farce/engine/jvm.rb +19 -0
  116. data/lib/farce/engine/ruby/3.4/fiber_scheduler.rb +20 -0
  117. data/lib/farce/engine/ruby/3.4/port.rb +186 -0
  118. data/lib/farce/engine/ruby/3.4/ractor_methods.rb +26 -0
  119. data/lib/farce/engine/ruby/3.4/ractor_selector.rb +92 -0
  120. data/lib/farce/engine/ruby/3.4/vault.rb +56 -0
  121. data/lib/farce/engine/ruby/4.0/port.rb +19 -0
  122. data/lib/farce/engine/ruby/4.0/ractor_methods.rb +20 -0
  123. data/lib/farce/engine/ruby/4.0/ractor_selector.rb +108 -0
  124. data/lib/farce/engine/ruby/4.0/vault.rb +74 -0
  125. data/lib/farce/engine/ruby/4.1/port.rb +21 -0
  126. data/lib/farce/engine/ruby/4.1/ractor_methods.rb +22 -0
  127. data/lib/farce/engine/ruby/4.1/ractor_selector.rb +25 -0
  128. data/lib/farce/engine/ruby/4.1/vault.rb +5 -0
  129. data/lib/farce/engine/ruby/fiber_scheduler.rb +32 -0
  130. data/lib/farce/engine/ruby/key_lock_map.rb +28 -0
  131. data/lib/farce/engine/ruby/shared/lease.rb +26 -0
  132. data/lib/farce/engine/ruby/shared/lease_pool.rb +24 -0
  133. data/lib/farce/engine/ruby/shared/main_scheduler.rb +9 -0
  134. data/lib/farce/engine/ruby/shared/parallel_scheduler.rb +9 -0
  135. data/lib/farce/engine/ruby/shared/proxy_owner.rb +31 -0
  136. data/lib/farce/engine/ruby/shared/ractor_methods.rb +34 -0
  137. data/lib/farce/engine/ruby/shared/ractor_selector.rb +328 -0
  138. data/lib/farce/engine/ruby/shared/strict_map.rb +13 -0
  139. data/lib/farce/engine/ruby/shared/unshared_vector.rb +14 -0
  140. data/lib/farce/engine/ruby/shared/vault.rb +144 -0
  141. data/lib/farce/engine/ruby/shared/vault_weak_map.rb +336 -0
  142. data/lib/farce/engine/ruby/shared/weak_atom.rb +54 -0
  143. data/lib/farce/engine/ruby/shared/weak_map.rb +413 -0
  144. data/lib/farce/engine/ruby.rb +130 -0
  145. data/lib/farce/engine/shared/atom.rb +10 -0
  146. data/lib/farce/engine/shared/exchanger.rb +130 -0
  147. data/lib/farce/engine/shared/identity_key.rb +22 -0
  148. data/lib/farce/engine/shared/lease.rb +10 -0
  149. data/lib/farce/engine/shared/lease_pool.rb +10 -0
  150. data/lib/farce/engine/shared/main_scheduler.rb +20 -0
  151. data/lib/farce/engine/shared/map_key_coordination.rb +226 -0
  152. data/lib/farce/engine/shared/parallel_scheduler.rb +9 -0
  153. data/lib/farce/engine/shared/port.rb +44 -0
  154. data/lib/farce/engine/shared/portable_bounded_map.rb +619 -0
  155. data/lib/farce/engine/shared/proxy_owner.rb +43 -0
  156. data/lib/farce/engine/shared/queue.rb +233 -0
  157. data/lib/farce/engine/shared/ractor_methods.rb +73 -0
  158. data/lib/farce/engine/shared/rebindable.rb +15 -0
  159. data/lib/farce/engine/shared/strict_atom.rb +73 -0
  160. data/lib/farce/engine/shared/strict_map.rb +117 -0
  161. data/lib/farce/engine/shared/strict_queue_values.rb +27 -0
  162. data/lib/farce/engine/shared/strict_tree_map.rb +56 -0
  163. data/lib/farce/engine/shared/transaction_map_backend.rb +20 -0
  164. data/lib/farce/engine/shared/trie.rb +317 -0
  165. data/lib/farce/engine/shared/trie_builder.rb +199 -0
  166. data/lib/farce/engine/shared/unshareable.rb +14 -0
  167. data/lib/farce/engine/shared/unshared_atom.rb +256 -0
  168. data/lib/farce/engine/shared/unshared_priority_queue.rb +9 -0
  169. data/lib/farce/engine/shared/unshared_queue.rb +18 -0
  170. data/lib/farce/engine/shared/unshared_signal.rb +21 -0
  171. data/lib/farce/engine/shared/unshared_vector.rb +377 -0
  172. data/lib/farce/engine/shared/unshared_weak_atom.rb +30 -0
  173. data/lib/farce/engine/shared/unshared_weak_map.rb +445 -0
  174. data/lib/farce/engine/shared/vault.rb +31 -0
  175. data/lib/farce/engine/shared/vector.rb +83 -0
  176. data/lib/farce/engine/shared/weak_atom/base.rb +189 -0
  177. data/lib/farce/engine/shared/weak_atom.rb +20 -0
  178. data/lib/farce/engine/shared/weak_map/cell.rb +99 -0
  179. data/lib/farce/engine/shared/weak_map/index.rb +249 -0
  180. data/lib/farce/engine/shared/weak_map/lock.rb +80 -0
  181. data/lib/farce/engine/shared/weak_map/reference.rb +33 -0
  182. data/lib/farce/engine/shared.rb +63 -0
  183. data/lib/farce/engine/truffleruby/fiber_scheduler.rb +13 -0
  184. data/lib/farce/engine/truffleruby/lock.rb +121 -0
  185. data/lib/farce/engine/truffleruby/map.rb +656 -0
  186. data/lib/farce/engine/truffleruby/native/counter.rb +105 -0
  187. data/lib/farce/engine/truffleruby/native/flag.rb +77 -0
  188. data/lib/farce/engine/truffleruby/native/ordered_array_support.rb +67 -0
  189. data/lib/farce/engine/truffleruby/native/priority_queue.rb +388 -0
  190. data/lib/farce/engine/truffleruby/native/tree_map.rb +51 -0
  191. data/lib/farce/engine/truffleruby/native/unsafe_tree_map.rb +350 -0
  192. data/lib/farce/engine/truffleruby/signal.rb +77 -0
  193. data/lib/farce/engine/truffleruby.rb +148 -0
  194. data/lib/farce/envelope.rb +335 -0
  195. data/lib/farce/error.rb +35 -0
  196. data/lib/farce/exchanger.rb +41 -0
  197. data/lib/farce/flag.rb +27 -0
  198. data/lib/farce/integrations/active_support/blank.rb +68 -0
  199. data/lib/farce/integrations/active_support/clock.rb +17 -0
  200. data/lib/farce/integrations/active_support/duplicable.rb +78 -0
  201. data/lib/farce/integrations/active_support/map.rb +175 -0
  202. data/lib/farce/integrations/active_support/set.rb +60 -0
  203. data/lib/farce/integrations/active_support/value_serialization.rb +17 -0
  204. data/lib/farce/integrations/active_support/vector.rb +231 -0
  205. data/lib/farce/integrations/active_support.rb +9 -0
  206. data/lib/farce/integrations/activesupport.rb +5 -0
  207. data/lib/farce/integrations/bson.rb +108 -0
  208. data/lib/farce/integrations/cbor.rb +51 -0
  209. data/lib/farce/integrations/concurrent.rb +112 -0
  210. data/lib/farce/integrations/dry-types.rb +5 -0
  211. data/lib/farce/integrations/dry_types.rb +694 -0
  212. data/lib/farce/integrations/json.rb +7 -0
  213. data/lib/farce/integrations/msgpack.rb +112 -0
  214. data/lib/farce/integrations/oj.rb +69 -0
  215. data/lib/farce/integrations/psych.rb +191 -0
  216. data/lib/farce/integrations/ractor-sharing.rb +5 -0
  217. data/lib/farce/integrations/ractor-tmvar.rb +5 -0
  218. data/lib/farce/integrations/ractor_sharing.rb +172 -0
  219. data/lib/farce/integrations/ractor_tmvar.rb +64 -0
  220. data/lib/farce/integrations/shared/to_json.rb +42 -0
  221. data/lib/farce/integrations/sorted_set.rb +11 -0
  222. data/lib/farce/integrations/weakref.rb +38 -0
  223. data/lib/farce/integrations/yajl.rb +10 -0
  224. data/lib/farce/integrations.rb +156 -0
  225. data/lib/farce/internal/_frozen_config.rb +11 -0
  226. data/lib/farce/internal/autoloads.rb +47 -0
  227. data/lib/farce/internal/blocking_priority_queue.rb +122 -0
  228. data/lib/farce/internal/converter.rb +106 -0
  229. data/lib/farce/internal/copyable.rb +31 -0
  230. data/lib/farce/internal/delegation.rb +20 -0
  231. data/lib/farce/internal/external_transaction.rb +59 -0
  232. data/lib/farce/internal/fake_ractor.rb +179 -0
  233. data/lib/farce/internal/freeze.rb +118 -0
  234. data/lib/farce/internal/inspect.rb +157 -0
  235. data/lib/farce/internal/key_lock_map.rb +29 -0
  236. data/lib/farce/internal/key_normalizer.rb +289 -0
  237. data/lib/farce/internal/lease_initialization.rb +115 -0
  238. data/lib/farce/internal/lease_map.rb +441 -0
  239. data/lib/farce/internal/lease_pool_state.rb +228 -0
  240. data/lib/farce/internal/lease_state.rb +342 -0
  241. data/lib/farce/internal/lease_waiting.rb +13 -0
  242. data/lib/farce/internal/managed_queue.rb +26 -0
  243. data/lib/farce/internal/map_value_modes.rb +227 -0
  244. data/lib/farce/internal/marshal_support.rb +227 -0
  245. data/lib/farce/internal/mixin.rb +20 -0
  246. data/lib/farce/internal/mutable_ordered_key_lock_map.rb +63 -0
  247. data/lib/farce/internal/noncopyable.rb +17 -0
  248. data/lib/farce/internal/ordered_key_lock_map.rb +69 -0
  249. data/lib/farce/internal/pool_supervisor.rb +41 -0
  250. data/lib/farce/internal/pool_worker.rb +61 -0
  251. data/lib/farce/internal/portable_transaction/reservation_entry.rb +29 -0
  252. data/lib/farce/internal/portable_transaction/strong_map_entry.rb +137 -0
  253. data/lib/farce/internal/portable_transaction/strong_map_size_entry.rb +44 -0
  254. data/lib/farce/internal/portable_transaction/tree_entry.rb +64 -0
  255. data/lib/farce/internal/portable_transaction.rb +164 -0
  256. data/lib/farce/internal/proxy_owner_notifications.rb +29 -0
  257. data/lib/farce/internal/reservation_waiting.rb +24 -0
  258. data/lib/farce/internal/scheduled_task.rb +48 -0
  259. data/lib/farce/internal/scheduler_io.rb +171 -0
  260. data/lib/farce/internal/scheduler_lifecycle.rb +314 -0
  261. data/lib/farce/internal/select_scheduler.rb +235 -0
  262. data/lib/farce/internal/storage.rb +162 -0
  263. data/lib/farce/internal/strict_lease.rb +30 -0
  264. data/lib/farce/internal/strict_lease_map.rb +20 -0
  265. data/lib/farce/internal/strict_lease_pool.rb +29 -0
  266. data/lib/farce/internal/thread_pool.rb +166 -0
  267. data/lib/farce/internal/transaction_conflict.rb +9 -0
  268. data/lib/farce/internal/transaction_freeze_guard.rb +30 -0
  269. data/lib/farce/internal/transaction_map_snapshot.rb +123 -0
  270. data/lib/farce/internal/undefined.rb +22 -0
  271. data/lib/farce/internal/unshared_lease.rb +29 -0
  272. data/lib/farce/internal/unshared_lease_pool.rb +34 -0
  273. data/lib/farce/internal/unshared_queue_waiting.rb +28 -0
  274. data/lib/farce/internal/value_serialization.rb +13 -0
  275. data/lib/farce/internal/weak_map_value_modes.rb +58 -0
  276. data/lib/farce/internal/weak_mode_manager.rb +26 -0
  277. data/lib/farce/internal.rb +129 -0
  278. data/lib/farce/lazy.rb +100 -0
  279. data/lib/farce/lazy_ref.rb +40 -0
  280. data/lib/farce/lease.rb +28 -0
  281. data/lib/farce/lease_map.rb +34 -0
  282. data/lib/farce/lease_pool.rb +29 -0
  283. data/lib/farce/lfu_map.rb +53 -0
  284. data/lib/farce/local/atom.rb +23 -0
  285. data/lib/farce/local/counter.rb +88 -0
  286. data/lib/farce/local/flag.rb +65 -0
  287. data/lib/farce/local/lazy.rb +38 -0
  288. data/lib/farce/local/lazy_ref.rb +36 -0
  289. data/lib/farce/local/lease.rb +77 -0
  290. data/lib/farce/local/lease_map.rb +80 -0
  291. data/lib/farce/local/lease_pool.rb +38 -0
  292. data/lib/farce/local/lfu_map.rb +55 -0
  293. data/lib/farce/local/lru_map.rb +62 -0
  294. data/lib/farce/local/map.rb +47 -0
  295. data/lib/farce/local/molecule.rb +45 -0
  296. data/lib/farce/local/priority_queue.rb +53 -0
  297. data/lib/farce/local/queue.rb +32 -0
  298. data/lib/farce/local/scoped.rb +166 -0
  299. data/lib/farce/local/set.rb +18 -0
  300. data/lib/farce/local/sorted_set.rb +50 -0
  301. data/lib/farce/local/timer_queue.rb +41 -0
  302. data/lib/farce/local/tree_map.rb +47 -0
  303. data/lib/farce/local/vector.rb +30 -0
  304. data/lib/farce/local/weak_atom.rb +23 -0
  305. data/lib/farce/local/weak_key_map.rb +39 -0
  306. data/lib/farce/local/weak_map.rb +39 -0
  307. data/lib/farce/local/weak_set.rb +18 -0
  308. data/lib/farce/local/weak_value_map.rb +39 -0
  309. data/lib/farce/local.rb +35 -0
  310. data/lib/farce/lock.rb +54 -0
  311. data/lib/farce/lru_map.rb +63 -0
  312. data/lib/farce/map.rb +58 -0
  313. data/lib/farce/mode_manager.rb +142 -0
  314. data/lib/farce/molecule.rb +60 -0
  315. data/lib/farce/mutable.rb +171 -0
  316. data/lib/farce/pool.rb +332 -0
  317. data/lib/farce/port.rb +134 -0
  318. data/lib/farce/priority_queue.rb +70 -0
  319. data/lib/farce/proxy/register.rb +101 -0
  320. data/lib/farce/proxy/supervisor.rb +137 -0
  321. data/lib/farce/proxy/wrapper.rb +49 -0
  322. data/lib/farce/proxy.rb +291 -0
  323. data/lib/farce/queue.rb +60 -0
  324. data/lib/farce/ractor.rb +269 -0
  325. data/lib/farce/read_write_lock.rb +256 -0
  326. data/lib/farce/reference.rb +158 -0
  327. data/lib/farce/resolv/dns.rb +42 -0
  328. data/lib/farce/resolv.rb +85 -0
  329. data/lib/farce/scheduler.rb +536 -0
  330. data/lib/farce/set.rb +39 -0
  331. data/lib/farce/shareable.rb +150 -0
  332. data/lib/farce/signal.rb +97 -0
  333. data/lib/farce/sorted_set.rb +36 -0
  334. data/lib/farce/strict/atom.rb +34 -0
  335. data/lib/farce/strict/counter.rb +10 -0
  336. data/lib/farce/strict/exchanger.rb +25 -0
  337. data/lib/farce/strict/flag.rb +10 -0
  338. data/lib/farce/strict/lazy.rb +26 -0
  339. data/lib/farce/strict/lazy_ref.rb +26 -0
  340. data/lib/farce/strict/lease.rb +27 -0
  341. data/lib/farce/strict/lease_map.rb +33 -0
  342. data/lib/farce/strict/lease_pool.rb +28 -0
  343. data/lib/farce/strict/lfu_map.rb +27 -0
  344. data/lib/farce/strict/lru_map.rb +28 -0
  345. data/lib/farce/strict/map.rb +54 -0
  346. data/lib/farce/strict/molecule.rb +16 -0
  347. data/lib/farce/strict/port.rb +40 -0
  348. data/lib/farce/strict/priority_queue.rb +20 -0
  349. data/lib/farce/strict/queue.rb +17 -0
  350. data/lib/farce/strict/set.rb +14 -0
  351. data/lib/farce/strict/sorted_set.rb +19 -0
  352. data/lib/farce/strict/timer_queue.rb +13 -0
  353. data/lib/farce/strict/tree_map.rb +27 -0
  354. data/lib/farce/strict/vector.rb +24 -0
  355. data/lib/farce/strict/weak_atom.rb +31 -0
  356. data/lib/farce/strict/weak_key_map.rb +49 -0
  357. data/lib/farce/strict/weak_map.rb +49 -0
  358. data/lib/farce/strict/weak_set.rb +14 -0
  359. data/lib/farce/strict/weak_value_map.rb +48 -0
  360. data/lib/farce/strict.rb +28 -0
  361. data/lib/farce/system.rb +40 -0
  362. data/lib/farce/thread_scheduler.rb +70 -0
  363. data/lib/farce/timer_queue.rb +66 -0
  364. data/lib/farce/transaction/atom.rb +73 -0
  365. data/lib/farce/transaction/map.rb +25 -0
  366. data/lib/farce/transaction/map_operations.rb +143 -0
  367. data/lib/farce/transaction/molecule.rb +90 -0
  368. data/lib/farce/transaction/mutable.rb +61 -0
  369. data/lib/farce/transaction/set.rb +17 -0
  370. data/lib/farce/transaction/set_operations.rb +48 -0
  371. data/lib/farce/transaction/sorted_set.rb +18 -0
  372. data/lib/farce/transaction/tree_map.rb +78 -0
  373. data/lib/farce/transaction/vector.rb +139 -0
  374. data/lib/farce/transaction/wrapper.rb +202 -0
  375. data/lib/farce/transaction.rb +343 -0
  376. data/lib/farce/tree_map.rb +56 -0
  377. data/lib/farce/unsafe/lfu_map.rb +36 -0
  378. data/lib/farce/unsafe/lru_map.rb +36 -0
  379. data/lib/farce/unsafe/tree_map.rb +21 -0
  380. data/lib/farce/unsafe.rb +29 -0
  381. data/lib/farce/unshareable.rb +67 -0
  382. data/lib/farce/unshared/atom.rb +24 -0
  383. data/lib/farce/unshared/counter.rb +10 -0
  384. data/lib/farce/unshared/flag.rb +10 -0
  385. data/lib/farce/unshared/lazy.rb +38 -0
  386. data/lib/farce/unshared/lazy_ref.rb +26 -0
  387. data/lib/farce/unshared/lease.rb +27 -0
  388. data/lib/farce/unshared/lease_map.rb +26 -0
  389. data/lib/farce/unshared/lease_pool.rb +25 -0
  390. data/lib/farce/unshared/lfu_map.rb +20 -0
  391. data/lib/farce/unshared/lru_map.rb +20 -0
  392. data/lib/farce/unshared/map.rb +49 -0
  393. data/lib/farce/unshared/molecule.rb +16 -0
  394. data/lib/farce/unshared/priority_queue.rb +16 -0
  395. data/lib/farce/unshared/queue.rb +27 -0
  396. data/lib/farce/unshared/set.rb +14 -0
  397. data/lib/farce/unshared/sorted_set.rb +28 -0
  398. data/lib/farce/unshared/timer_queue.rb +16 -0
  399. data/lib/farce/unshared/tree_map.rb +21 -0
  400. data/lib/farce/unshared/vector.rb +19 -0
  401. data/lib/farce/unshared/weak_atom.rb +31 -0
  402. data/lib/farce/unshared/weak_key_map.rb +44 -0
  403. data/lib/farce/unshared/weak_map.rb +44 -0
  404. data/lib/farce/unshared/weak_set.rb +14 -0
  405. data/lib/farce/unshared/weak_value_map.rb +43 -0
  406. data/lib/farce/unshared.rb +28 -0
  407. data/lib/farce/vector.rb +228 -0
  408. data/lib/farce/version.rb +8 -0
  409. data/lib/farce/walker/definitions.rb +186 -0
  410. data/lib/farce/walker/modification.rb +355 -0
  411. data/lib/farce/walker.rb +319 -0
  412. data/lib/farce/weak_atom.rb +121 -0
  413. data/lib/farce/weak_key_map.rb +31 -0
  414. data/lib/farce/weak_map.rb +33 -0
  415. data/lib/farce/weak_ref.rb +67 -0
  416. data/lib/farce/weak_set.rb +82 -0
  417. data/lib/farce/weak_value.rb +195 -0
  418. data/lib/farce/weak_value_map.rb +32 -0
  419. data/lib/farce.rb +519 -0
  420. metadata +435 -8
@@ -0,0 +1,910 @@
1
+ # frozen_string_literal: true
2
+ # shareable_constant_value: literal
3
+ # warn_indent: true
4
+
5
+ module Farce
6
+ module Abstract
7
+ # @abstract Common interface for concurrent sets backed by Farce maps.
8
+ # Membership changes are atomic per element. Operations involving several
9
+ # elements are not atomic as a whole. Iteration streams without first copying
10
+ # every element. Structural changes during traversal can invalidate it.
11
+ # Identity comparison and normalization are fixed at construction, so this
12
+ # class does not provide Set#compare_by_identity or Set#reset.
13
+ class Set < Collection
14
+ include Internal::MarshalSupport::Initialize
15
+
16
+ # A shareable, immutable key used by mode-backed sets. Structural keys
17
+ # contain an insertion-time snapshot. Identity keys contain either the
18
+ # shareable element itself or an opaque token.
19
+ class MembershipKey
20
+ attr_reader :value
21
+
22
+ def initialize(value, identity: false)
23
+ @value = value
24
+ @identity = identity
25
+ @hash = identity ? value.__id__.hash : value.hash
26
+ freeze
27
+ end
28
+
29
+ def eql?(other)
30
+ return false unless other.instance_of?(self.class) && @identity == other.identity?
31
+ @identity ? @value.equal?(other.value) : @value.eql?(other.value)
32
+ end
33
+ alias == eql?
34
+
35
+ def hash = [self.class, @identity, @hash].hash
36
+ def identity? = @identity
37
+ end
38
+ private_constant :MembershipKey
39
+
40
+ # Keeps the manager that created a payload beside it. Derived sets can
41
+ # retain the stored representation without opening move envelopes.
42
+ class StoredEntry
43
+ include Shareable::Immutable
44
+
45
+ attr_reader :manager, :payload
46
+
47
+ def initialize(manager, payload)
48
+ @manager = manager
49
+ @payload = payload
50
+ super()
51
+ end
52
+ end
53
+ private_constant :StoredEntry
54
+
55
+ IDENTITY_TOKENS = Local::WeakKeyMap.new(compare_keys_by_identity: true)
56
+ MISSING_KEY = Object.new.freeze
57
+ private_constant :IDENTITY_TOKENS, :MISSING_KEY
58
+
59
+ # Construct a set, optionally transforming each initial element before normalization.
60
+ # @param enumerable [#each, nil] The initial elements, or nil for an empty set.
61
+ # @param normalize [Symbol, Proc, Hash, Farce::Abstract::Map, nil] The element normalizer.
62
+ # @param compare_by_identity [Boolean] Whether membership uses object identity.
63
+ # @param mode [Symbol] The default transfer mode for Farce::Set, Farce::SortedSet,
64
+ # and Farce::WeakSet. Defaults to :copy for strong sets and :raise for WeakSet.
65
+ # @param options [Hash] Additional options for the selected variant.
66
+ # @option options [Symbol] scope (:ractor) The scope used by Farce::Local variants.
67
+ # @yield [element] Optionally transform each initial element before storage.
68
+ # @yieldparam element [BasicObject] An element from enumerable.
69
+ # @yieldreturn [BasicObject] The element to normalize and store.
70
+ # @return [Farce::Abstract::Set] The new set.
71
+ def initialize(
72
+ enumerable = nil, normalize: nil, compare_by_identity: false, mode: UNDEFINED, **options, &transform
73
+ )
74
+ unknown = options.keys - [:scope]
75
+
76
+ unless unknown.empty?
77
+ label = unknown.size == 1 ? "keyword" : "keywords"
78
+ raise ArgumentError, "unknown #{label}: #{unknown.map(&:inspect).join(", ")}"
79
+ end
80
+
81
+ shareable = !is_a?(Unshareable)
82
+
83
+ validate_boolean!(:compare_by_identity, compare_by_identity)
84
+
85
+ @compare_by_identity = compare_by_identity
86
+ @normalizer = Internal::KeyNormalizer.build(normalize, shareable:)
87
+ restoring = Internal::KeyNormalizer.restoration?(normalize)
88
+
89
+ initialize_value_mode(mode)
90
+ elements = input_elements(enumerable) unless enumerable.nil?
91
+ elements.map! { transform.call(it) } if transform && elements
92
+ elements.map! { normalize_element(it) } if elements && @normalizer && !restoring
93
+
94
+ if value_modes?
95
+ @map = new_map(nil, compare_keys_by_identity: false, **options)
96
+ elements&.each { add_normalized(it) }
97
+ else
98
+ entries = elements&.map { [it, true] }
99
+ @map = new_map(entries, compare_keys_by_identity: compare_by_identity, **options)
100
+ end
101
+
102
+ super()
103
+ end
104
+
105
+ # @api private
106
+ def marshal_dump
107
+ normalizer = @normalizer && Internal::KeyNormalizer.dump(@normalizer)
108
+ contents =
109
+ if value_modes?
110
+ each_stored.map do |key, entry|
111
+ identity = identity_storage_key?(key)
112
+ snapshot = identity ? key.value : key
113
+ [identity, Internal::MarshalSupport.value(snapshot), entry.manager,
114
+ Internal::MarshalSupport.value(entry.payload)]
115
+ end
116
+ else
117
+ @map.marshal_dump
118
+ end
119
+ [1, contents, compare_by_identity?, normalizer, respond_to?(:mode) ? mode : nil, frozen?]
120
+ end
121
+
122
+ # @api private
123
+ def marshal_load(data)
124
+ contents, identity, normalizer, mode, frozen = Internal::MarshalSupport.payload(data, 5)
125
+ @compare_by_identity = identity
126
+ @normalizer = Internal::KeyNormalizer.build(
127
+ normalizer && Internal::KeyNormalizer.restore(normalizer),
128
+ shareable: !is_a?(Unshareable),
129
+ )
130
+ initialize_value_mode(mode || UNDEFINED)
131
+ if value_modes?
132
+ @map = new_map(nil, compare_keys_by_identity: false)
133
+ publish_marshaled
134
+ contents.each do |identity_key, snapshot, manager, stored|
135
+ key = Internal::MarshalSupport.restore_value(snapshot)
136
+ key = MembershipKey.new(key, identity: true) if identity_key
137
+ value = Internal::MarshalSupport.restore_value(stored)
138
+ add_stored(key, StoredEntry.new(manager, value))
139
+ end
140
+ else
141
+ # Select the backing class locally without recording its private name.
142
+ @map = new_map(nil, compare_keys_by_identity: identity).class.allocate
143
+ @map.marshal_load(contents)
144
+ publish_marshaled
145
+ end
146
+ Internal::MarshalSupport.freeze(self, frozen)
147
+ end
148
+
149
+ # @api private
150
+ def transaction_wrapper(transaction) = Transaction::Set.new(transaction, self, @map)
151
+
152
+ # Add an element and return self.
153
+ # @param element [BasicObject] The element to normalize and store.
154
+ # @param mode [Symbol, nil] Override the transfer mode for this insertion. Only
155
+ # Farce::Set, Farce::SortedSet, and Farce::WeakSet accept this option. Nil uses the set's default.
156
+ # @return [self] The set.
157
+ def add(element, mode: UNDEFINED)
158
+ check_frozen!
159
+ add_normalized(normalize_element(element), mode:)
160
+ self
161
+ end
162
+ alias << add
163
+
164
+ # Add an absent element and return self, or nil if it was already present.
165
+ # @param element [BasicObject] The element to normalize and store.
166
+ # @param mode [Symbol, nil] Override the transfer mode for this insertion. Only
167
+ # Farce::Set, Farce::SortedSet, and Farce::WeakSet accept this option. Nil uses the set's default.
168
+ # @return [self, nil] The set if added, otherwise nil.
169
+ def add?(element, mode: UNDEFINED) # rubocop:disable Naming/PredicateMethod
170
+ check_frozen!
171
+ added = add_normalized?(normalize_element(element), mode:)
172
+ self if added
173
+ end
174
+
175
+ # Remove an element and return self.
176
+ # @param element [BasicObject] The element to normalize and look up.
177
+ # @return [self] The set.
178
+ def delete(element)
179
+ check_frozen!
180
+ element = normalize_element(element)
181
+ if value_modes?
182
+ key = lookup_key(element)
183
+ @map.delete(key) unless MISSING_KEY.equal?(key)
184
+ else
185
+ @map.delete(element)
186
+ end
187
+ self
188
+ end
189
+
190
+ # Remove an element and return self, or nil if it was absent.
191
+ # @param element [BasicObject] The element to normalize and look up.
192
+ # @return [self, nil] The set if removed, otherwise nil.
193
+ def delete?(element) # rubocop:disable Naming/PredicateMethod
194
+ check_frozen!
195
+ element = normalize_element(element)
196
+ removed = if value_modes?
197
+ key = lookup_key(element)
198
+ @map.delete(key) unless MISSING_KEY.equal?(key)
199
+ else
200
+ @map.delete(element)
201
+ end
202
+ self if removed
203
+ end
204
+
205
+ # Return whether an element is present.
206
+ # @param element [BasicObject] The element to normalize and look up.
207
+ # @return [Boolean] Whether the normalized element is present.
208
+ def include?(element)
209
+ element = normalize_element(element)
210
+ return @map.key?(element) unless value_modes?
211
+ key = lookup_key(element)
212
+ !MISSING_KEY.equal?(key) && @map.key?(key)
213
+ end
214
+ alias member? include?
215
+ alias === include?
216
+
217
+ # Iterate over the elements currently present without first copying all elements.
218
+ # @yield [element] Visit each element. Returns an Enumerator without a block.
219
+ # @yieldparam element [BasicObject] The current element.
220
+ # @yieldreturn [void] The result is ignored.
221
+ # @return [self, Enumerator] The set, or an Enumerator without a block.
222
+ def each
223
+ return enum_for(__method__) { size } unless block_given?
224
+ each_element { yield it }
225
+ self
226
+ end
227
+
228
+ # Return the number of elements currently present.
229
+ # @return [Integer]
230
+ def size = @map.size
231
+
232
+ # Remove all elements and return self.
233
+ # @return [self]
234
+ def clear
235
+ check_frozen!
236
+ @map.clear
237
+ self
238
+ end
239
+
240
+ # Add all elements yielded by each enumerable.
241
+ # @param enumerables [Array<#each>] The collections of elements to add.
242
+ # @return [self] The set.
243
+ def merge(*enumerables)
244
+ check_frozen!
245
+ enumerables.each do |enumerable|
246
+ if canonical_compatible?(enumerable)
247
+ enumerable.each_stored { |key, value| add_stored(key, value) }
248
+ else
249
+ each_input(enumerable) { add(it) }
250
+ end
251
+ end
252
+ self
253
+ end
254
+
255
+ # Remove every element yielded by enumerable.
256
+ # @param enumerable [#each] The elements to remove.
257
+ # @return [self] The set.
258
+ def subtract(enumerable)
259
+ check_frozen!
260
+ if canonical_compatible?(enumerable)
261
+ retained = membership_index(enumerable)
262
+ @map.delete_if { |element, _| retained.key?(element) }
263
+ else
264
+ each_input(enumerable) do |element|
265
+ key = lookup_key(normalize_element(element))
266
+ @map.delete(key) unless MISSING_KEY.equal?(key)
267
+ end
268
+ end
269
+ self
270
+ end
271
+
272
+ # Remove elements accepted by the block.
273
+ # @yield [element] Test each element. Returns an Enumerator without a block.
274
+ # @yieldparam element [BasicObject] The current element.
275
+ # @yieldreturn [BasicObject] A truthy value to remove the element.
276
+ # @return [self, Enumerator] The set, or an Enumerator without a block.
277
+ def delete_if
278
+ return enum_for(__method__) { size } unless block_given?
279
+ check_frozen!
280
+ each_stored { |key, value| @map.delete(key) if yield(public_stored(key, value)) }
281
+ self
282
+ end
283
+
284
+ # Keep elements accepted by the block.
285
+ # @yield [element] Test each element. Returns an Enumerator without a block.
286
+ # @yieldparam element [BasicObject] The current element.
287
+ # @yieldreturn [BasicObject] A truthy value to keep the element.
288
+ # @return [self, Enumerator] The set, or an Enumerator without a block.
289
+ def keep_if
290
+ return enum_for(__method__) { size } unless block_given?
291
+ check_frozen!
292
+ each_stored { |key, value| @map.delete(key) unless yield(public_stored(key, value)) }
293
+ self
294
+ end
295
+
296
+ # Return a same-kind set containing elements accepted by the block.
297
+ # @yield [element] Test each element. Returns an Enumerator without a block.
298
+ # @yieldparam element [BasicObject] The current element.
299
+ # @yieldreturn [BasicObject] A truthy value to keep the element.
300
+ # @return [Farce::Abstract::Set, Enumerator] A new set, or an Enumerator without a block.
301
+ def select
302
+ return enum_for(__method__) { size } unless block_given?
303
+ dup.filter_backend! { yield it }
304
+ end
305
+ alias filter select
306
+
307
+ # Return a same-kind set without elements accepted by the block.
308
+ # @yield [element] Test each element. Returns an Enumerator without a block.
309
+ # @yieldparam element [BasicObject] The current element.
310
+ # @yieldreturn [BasicObject] A truthy value to exclude the element.
311
+ # @return [Farce::Abstract::Set, Enumerator] A new set, or an Enumerator without a block.
312
+ def reject
313
+ return enum_for(__method__) { size } unless block_given?
314
+ dup.filter_backend! { !yield(it) }
315
+ end
316
+
317
+ # Remove elements accepted by the block, returning nil when unchanged.
318
+ # @yield [element] Test each element. Returns an Enumerator without a block.
319
+ # @yieldparam element [BasicObject] The current element.
320
+ # @yieldreturn [BasicObject] A truthy value to remove the element.
321
+ # @return [self, nil, Enumerator] The changed set, nil if unchanged, or an Enumerator without a block.
322
+ def reject!
323
+ return enum_for(__method__) { size } unless block_given?
324
+ check_frozen!
325
+ changed = false
326
+ each_stored do |key, value|
327
+ changed = true if yield(public_stored(key, value)) && @map.delete(key)
328
+ end
329
+ self if changed
330
+ end
331
+
332
+ # Keep elements accepted by the block, returning nil when unchanged.
333
+ # @yield [element] Test each element. Returns an Enumerator without a block.
334
+ # @yieldparam element [BasicObject] The current element.
335
+ # @yieldreturn [BasicObject] A truthy value to keep the element.
336
+ # @return [self, nil, Enumerator] The changed set, nil if unchanged, or an Enumerator without a block.
337
+ def select!
338
+ return enum_for(__method__) { size } unless block_given?
339
+ check_frozen!
340
+ changed = false
341
+ each_stored do |key, value|
342
+ changed = true if !yield(public_stored(key, value)) && @map.delete(key)
343
+ end
344
+ self if changed
345
+ end
346
+ alias filter! select!
347
+
348
+ # Return the elements in an Array.
349
+ # @return [Array<BasicObject>] The observed elements.
350
+ def to_a = each.to_a
351
+
352
+ # Convert to a Ruby Set, or to an explicitly requested set class.
353
+ # @param klass [Class] The target set class. Defaults to Ruby's ::Set.
354
+ # @param arguments [Array<BasicObject>] Additional positional arguments for its constructor.
355
+ # @param options [Hash] Keyword arguments for its constructor.
356
+ # @yield [element] Optionally transform elements through the target constructor.
357
+ # @yieldparam element [BasicObject] The current element.
358
+ # @yieldreturn [BasicObject] The element to store in the target set.
359
+ # @return [::Set, Farce::Abstract::Set] A new Ruby Set by default. An explicit
360
+ # target class matching self returns self when no arguments, options, or block
361
+ # are supplied. Otherwise, returns a new instance of klass.
362
+ def to_set(klass = ::Set, *arguments, **options, &)
363
+ return self if klass == self.class && arguments.empty? && options.empty? && !block_given?
364
+ klass.new(self, *arguments, **options, &)
365
+ end
366
+
367
+ # Return a same-kind set containing elements from either operand.
368
+ # @param enumerables [Array<#each>] The collections of elements to include.
369
+ # @return [Farce::Abstract::Set] A new set of the same class with the same settings.
370
+ def union(*enumerables) = dup.merge(*enumerables)
371
+ alias | union
372
+ alias + union
373
+
374
+ # Return a same-kind set without elements from the enumerable.
375
+ # @param enumerable [#each] The elements to exclude.
376
+ # @return [Farce::Abstract::Set] A new set of the same class with the same settings.
377
+ def difference(enumerable) = dup.subtract(enumerable)
378
+ alias - difference
379
+
380
+ # Return a same-kind set containing elements also present in every enumerable.
381
+ # @param enumerables [Array<#each>] The collections whose members must be present.
382
+ # @return [Farce::Abstract::Set] A new set of the same class with the same settings.
383
+ def intersection(*enumerables)
384
+ copy = dup
385
+ indexes = enumerables.map { membership_index(it) }
386
+ copy.filter_stored! { |key, _| indexes.all? { it.key?(key) } }
387
+ copy
388
+ end
389
+ alias & intersection
390
+
391
+ # Return a same-kind set containing elements present in exactly one operand.
392
+ # @param other [#each] The other collection of elements.
393
+ # @return [Farce::Abstract::Set] A new set of the same class with the same settings.
394
+ def ^(other)
395
+ other = empty_copy.merge(other)
396
+ (self - other).merge(other - self)
397
+ end
398
+
399
+ # Return whether every element is present in the other set-like object.
400
+ # @param other [Farce::Abstract::Set, ::Set] The set to compare against.
401
+ # @return [Boolean] Whether the relation holds.
402
+ # @raise [ArgumentError] If other is not a Farce or Ruby set.
403
+ def subset?(other)
404
+ validate_set_like(other)
405
+ left = equality_index
406
+ right = relation_index(other)
407
+ left.size <= right.size && left.each_key.all? { right.key?(it) }
408
+ end
409
+ alias <= subset?
410
+
411
+ # Return whether this is a proper subset of the other set-like object.
412
+ # @param other [Farce::Abstract::Set, ::Set] The set to compare against.
413
+ # @return [Boolean] Whether the relation holds.
414
+ # @raise [ArgumentError] If other is not a Farce or Ruby set.
415
+ def proper_subset?(other)
416
+ validate_set_like(other)
417
+ left = equality_index
418
+ right = relation_index(other)
419
+ left.size < right.size && left.each_key.all? { right.key?(it) }
420
+ end
421
+ alias < proper_subset?
422
+
423
+ # Return whether all elements of the other set-like object are present.
424
+ # @param other [Farce::Abstract::Set, ::Set] The set to compare against.
425
+ # @return [Boolean] Whether the relation holds.
426
+ # @raise [ArgumentError] If other is not a Farce or Ruby set.
427
+ def superset?(other)
428
+ validate_set_like(other)
429
+ left = equality_index
430
+ right = relation_index(other)
431
+ left.size >= right.size && right.each_key.all? { left.key?(it) }
432
+ end
433
+ alias >= superset?
434
+
435
+ # Return whether this is a proper superset of the other set-like object.
436
+ # @param other [Farce::Abstract::Set, ::Set] The set to compare against.
437
+ # @return [Boolean] Whether the relation holds.
438
+ # @raise [ArgumentError] If other is not a Farce or Ruby set.
439
+ def proper_superset?(other)
440
+ validate_set_like(other)
441
+ left = equality_index
442
+ right = relation_index(other)
443
+ left.size > right.size && right.each_key.all? { left.key?(it) }
444
+ end
445
+ alias > proper_superset?
446
+
447
+ # Return whether this set and another set-like object share an element.
448
+ # @param other [Farce::Abstract::Set, ::Set] The set to compare against.
449
+ # @return [Boolean] Whether the relation holds.
450
+ # @raise [ArgumentError] If other is not a Farce or Ruby set.
451
+ def intersect?(other)
452
+ validate_set_like(other)
453
+ left = equality_index
454
+ right = relation_index(other)
455
+ left, right = right, left if right.size < left.size
456
+ left.each_key.any? { right.key?(it) }
457
+ end
458
+
459
+ # Return whether this set and another set-like object share no elements.
460
+ # @param other [Farce::Abstract::Set, ::Set] The set to compare against.
461
+ # @return [Boolean] Whether the relation holds.
462
+ # @raise [ArgumentError] If other is not a Farce or Ruby set.
463
+ def disjoint?(other) = !intersect?(other)
464
+
465
+ # Compare sets by the subset relation.
466
+ # @param other [BasicObject] The object to compare against.
467
+ # @return [Integer, nil] -1 for a proper subset, 0 for equal sets, 1 for a proper
468
+ # superset, or nil when neither relation holds or other is not a set.
469
+ def <=>(other)
470
+ return unless set_like?(other)
471
+ return 0 if self == other
472
+ return -1 if proper_subset?(other)
473
+ 1 if proper_superset?(other)
474
+ end
475
+
476
+ # Group elements by the block result.
477
+ # @yield [element] Choose a group for each element. Returns an Enumerator without a block.
478
+ # @yieldparam element [BasicObject] The current element.
479
+ # @yieldreturn [BasicObject] The group key.
480
+ # @return [Hash{BasicObject => Farce::Abstract::Set}, Enumerator] Group keys mapped
481
+ # to same-kind subsets, or an Enumerator without a block.
482
+ def classify
483
+ return enum_for(__method__) { size } unless block_given?
484
+ groups = {}
485
+ each_stored do |stored_key, stored_value|
486
+ element = public_stored(stored_key, stored_value)
487
+ key = yield element
488
+ (groups[key] ||= empty_copy).add_stored(stored_key, stored_value)
489
+ end
490
+ groups
491
+ end
492
+
493
+ # Divide elements into same-kind subsets.
494
+ # The outer result is an Unshared::Set that holds the subsets strongly.
495
+ #
496
+ # @overload divide { |element| ... }
497
+ # Group elements that produce the same block result.
498
+ # @yield [element] Choose a group for each element.
499
+ # @yieldparam element [BasicObject] The current element.
500
+ # @yieldreturn [BasicObject] The group key.
501
+ # @return [Farce::Unshared::Set] A set containing the same-kind subsets.
502
+ # @overload divide { |left, right| ... }
503
+ # Compute strongly connected components using a two-argument block.
504
+ # @yield [left, right] Test the directed relation between two elements.
505
+ # @yieldparam left [BasicObject] The source element.
506
+ # @yieldparam right [BasicObject] The candidate related element.
507
+ # @yieldreturn [BasicObject] A truthy value when left is related to right.
508
+ # @return [Farce::Unshared::Set] A set containing the same-kind components.
509
+ # @overload divide
510
+ # @return [Enumerator] An Enumerator that divides the set when given a block.
511
+ def divide(&block)
512
+ return enum_for(__method__) { size } unless block
513
+ groups = if block.arity == 2
514
+ # Ruby 3.4 also requires TSort here, inside Set#divide.
515
+ require "tsort"
516
+ divide_by_relation(&block)
517
+ else
518
+ classify(&block).values
519
+ end
520
+ Farce::Unshared::Set.new(groups)
521
+ end
522
+
523
+ # Return a flattened same-kind set.
524
+ # @return [Farce::Abstract::Set] A new set containing the recursively expanded members.
525
+ # @raise [ArgumentError] If a nested set contains itself recursively.
526
+ def flatten
527
+ copy = empty_copy
528
+ flatten_into(self, copy, ::Set.new.compare_by_identity)
529
+ copy
530
+ end
531
+
532
+ # Return whether membership uses object identity.
533
+ # @return [Boolean] Whether identity comparison is enabled.
534
+ def compare_by_identity? = @compare_by_identity
535
+
536
+ # Return whether elements are held weakly.
537
+ # @return [Boolean] Whether the set retains elements weakly.
538
+ def weak? = @map.weak_keys?
539
+
540
+ # Return whether this set has the same members as another set.
541
+ # @param other [BasicObject] The object to compare against.
542
+ # @return [Boolean] Whether other is a Farce set with compatible comparison settings and equal members.
543
+ def ==(other)
544
+ return true if equal?(other)
545
+ return false unless other.is_a?(Abstract::Set) && size == other.size
546
+ return false unless comparison_compatible?(other)
547
+ index = other.equality_index
548
+ equality_index.each_key.all? { index.key?(it) }
549
+ end
550
+
551
+ # Compare membership using eql?.
552
+ # @param other [BasicObject] The object to compare against.
553
+ # @return [Boolean] Whether other is a Farce set with compatible comparison settings and equal members.
554
+ def eql?(other)
555
+ return true if equal?(other)
556
+ return false unless other.is_a?(Abstract::Set) && size == other.size
557
+ return false unless comparison_compatible?(other)
558
+ index = other.equality_index
559
+ equality_index.each_key.all? { index.key?(it) }
560
+ end
561
+
562
+ # Return an order-independent hash derived from the members.
563
+ # @return [Integer] The hash code.
564
+ def hash
565
+ guard = recursion_guard(:farce_set_hash_guard)
566
+ return 0 if guard.key?(self)
567
+ guard[self] = true
568
+ entered = true
569
+ set = ::Set.new
570
+ set.merge(equality_index.keys).hash
571
+ ensure
572
+ guard&.delete(self) if entered
573
+ end
574
+
575
+ protected
576
+
577
+ def initialize_empty_copy(other)
578
+ initialize_copy(other, empty: true)
579
+ publish_shareable if is_a?(Shareable)
580
+ self
581
+ end
582
+
583
+ def each_stored(&block)
584
+ return enum_for(__method__) { size } unless block
585
+ @map.each_live(&block)
586
+ end
587
+
588
+ def filter_backend!(&keep)
589
+ check_frozen!
590
+ filter_stored! { |key, value| keep.call(public_stored(key, value)) }
591
+ end
592
+
593
+ def filter_stored!
594
+ check_frozen!
595
+ each_stored { |key, value| @map.delete(key) unless yield(key, value) }
596
+ self
597
+ end
598
+
599
+ def equality_index
600
+ each_stored.with_object({}) do |(key, _), index|
601
+ key = comparison_key_for_canonical(key) unless value_modes?
602
+ index[key] = true
603
+ end
604
+ end
605
+
606
+ def add_stored(key, value)
607
+ @map.store_if_absent(key) { value }
608
+ self
609
+ end
610
+
611
+ def public_stored(key, value) = value_modes? ? unwrap_entry(key, value) : key
612
+
613
+ def canonical_compatible?(other)
614
+ return false unless other.is_a?(Abstract::Set)
615
+ @normalizer.equal?(other.instance_variable_get(:@normalizer)) &&
616
+ compare_by_identity? == other.compare_by_identity? &&
617
+ value_modes? == other.value_modes? &&
618
+ ordered? == other.ordered?
619
+ end
620
+
621
+ def add_normalized(element, mode: UNDEFINED)
622
+ add_normalized?(element, mode:)
623
+ self
624
+ end
625
+
626
+ def value_modes? = false
627
+ def map_backend = @map
628
+ def ordered? = false
629
+
630
+ private
631
+
632
+ def each_for_inspect
633
+ each_stored { |key, entry| yield(value_modes? ? entry : key) }
634
+ end
635
+
636
+ def inspect_value(inspector, value)
637
+ if value_modes?
638
+ inspector.object(value.payload, value.manager)
639
+ else
640
+ super
641
+ end
642
+ end
643
+
644
+ def check_frozen! = Internal::Freeze.check(self)
645
+ def freeze_backend = @map
646
+
647
+ def initialize_copy(other, empty: false)
648
+ super(other)
649
+ @map = if empty
650
+ new_map(
651
+ nil,
652
+ compare_keys_by_identity: value_modes? ? false : other.compare_by_identity?,
653
+ **copy_map_options(other),
654
+ )
655
+ else
656
+ other.map_backend.dup
657
+ end
658
+ end
659
+
660
+ def recursion_guard(key)
661
+ Internal::Storage.fiber.store_if_absent(key) { {}.compare_by_identity }
662
+ end
663
+
664
+ def each_element
665
+ each_stored { |key, value| yield public_stored(key, value) }
666
+ end
667
+
668
+ def each_input(enumerable)
669
+ raise ArgumentError, "value must be enumerable" unless enumerable.respond_to?(:each)
670
+ enumerable.each { yield it }
671
+ end
672
+
673
+ def input_elements(enumerable)
674
+ raise ArgumentError, "value must be enumerable" unless enumerable.respond_to?(:each)
675
+ elements = []
676
+ enumerable.each { elements << it } # rubocop:disable Style/MapIntoArray
677
+ elements
678
+ end
679
+
680
+ def empty_copy
681
+ copy = self.class.allocate
682
+ instance_variables.each { |name| copy.instance_variable_set(name, instance_variable_get(name)) }
683
+ copy.initialize_empty_copy(self)
684
+ end
685
+
686
+ def map_canonical
687
+ copy = empty_copy
688
+ each { copy.add_normalized(yield(it)) }
689
+ copy
690
+ end
691
+
692
+ def membership_index(enumerable)
693
+ raise ArgumentError, "value must be enumerable" unless enumerable.respond_to?(:each)
694
+ index = {}
695
+ index.compare_by_identity if compare_by_identity? && !value_modes?
696
+ if canonical_compatible?(enumerable)
697
+ enumerable.each_stored { |key, _| index[key] = true }
698
+ else
699
+ enumerable.each do |element|
700
+ element = normalize_element(element)
701
+ if value_modes?
702
+ key = lookup_key(element)
703
+ index[key] = true unless MISSING_KEY.equal?(key)
704
+ else
705
+ index[element] = true
706
+ end
707
+ end
708
+ end
709
+ index
710
+ end
711
+
712
+ def normalize_element(element) = @normalizer ? @normalizer.call(element) : element
713
+
714
+ def initialize_value_mode(mode)
715
+ raise ArgumentError, "unknown keyword: :mode" unless UNDEFINED.equal?(mode)
716
+ end
717
+
718
+ def validate_boolean!(name, value)
719
+ return if value.equal?(true) || value.equal?(false)
720
+ raise ArgumentError, "#{name} must be a boolean"
721
+ end
722
+
723
+ def add_normalized?(element, mode: UNDEFINED)
724
+ raise ArgumentError, "unknown keyword: :mode" unless value_modes? || UNDEFINED.equal?(mode)
725
+ if value_modes?
726
+ add_mode_value?(element, mode:)
727
+ else
728
+ added = false
729
+ @map.store_if_absent(element) do
730
+ added = true
731
+ true
732
+ end
733
+ added
734
+ end
735
+ end
736
+
737
+ def add_mode_value?(element, mode:)
738
+ requested_mode = UNDEFINED.equal?(mode) || mode.nil? ? @manager.mode : mode
739
+ existing_key = lookup_key(element)
740
+ return false unless MISSING_KEY.equal?(existing_key) || !@map.key?(existing_key)
741
+
742
+ key, payload = prepare_mode_entry(element, requested_mode)
743
+ added = false
744
+
745
+ @map.store_if_absent(key) do
746
+ added = true
747
+ payload = @manager.wrap(element, mode: requested_mode) if UNDEFINED.equal?(payload)
748
+ StoredEntry.new(@manager, payload)
749
+ end
750
+ added
751
+ end
752
+
753
+ def prepare_mode_entry(element, requested_mode)
754
+ if identity_membership?(element) && !Ractor.shareable?(element)
755
+ case requested_mode
756
+ when :copy
757
+ raise ArgumentError, "identity comparison cannot copy a non-shareable element"
758
+ when :shareable_copy, :make_shareable
759
+ payload = @manager.wrap(element, mode: requested_mode)
760
+ return [insertion_key(payload), payload]
761
+ end
762
+ end
763
+
764
+ key = insertion_key(element)
765
+ [key, UNDEFINED]
766
+ end
767
+
768
+ def insertion_key(element)
769
+ return structural_key(element) unless identity_membership?(element)
770
+ token = IDENTITY_TOKENS[element]
771
+ return MembershipKey.new(token, identity: true) if token
772
+ return MembershipKey.new(element, identity: true) if Ractor.shareable?(element)
773
+
774
+ token = IDENTITY_TOKENS.store_if_absent(element) { Object.new.freeze }
775
+ MembershipKey.new(token, identity: true)
776
+ end
777
+
778
+ def lookup_key(element)
779
+ return element unless value_modes?
780
+ return structural_key(element) unless identity_membership?(element)
781
+ token = IDENTITY_TOKENS[element]
782
+ return MembershipKey.new(token, identity: true) if token
783
+ return MembershipKey.new(element, identity: true) if Ractor.shareable?(element)
784
+
785
+ MISSING_KEY
786
+ end
787
+
788
+ def structural_key(element)
789
+ return element if Ractor.shareable?(element)
790
+
791
+ snapshot = Ractor.make_shareable(element, copy: true)
792
+ unless snapshot.eql?(element) && element.eql?(snapshot) && snapshot.hash == element.hash
793
+ raise ArgumentError, "element cannot be represented by a stable equality snapshot"
794
+ end
795
+ snapshot
796
+ end
797
+
798
+ def identity_membership?(element)
799
+ compare_by_identity? || element.method(:eql?).owner == Kernel
800
+ rescue NameError
801
+ true
802
+ end
803
+
804
+ def unwrap_entry(key, entry)
805
+ element = entry.manager.unwrap(entry.payload)
806
+ if identity_storage_key?(key) && !Ractor.shareable?(element)
807
+ token = key.value
808
+ IDENTITY_TOKENS.store_if_absent(element) { token }
809
+ end
810
+ element
811
+ end
812
+
813
+ def stored_mode(entry)
814
+ case entry.payload
815
+ when Envelope::Copy then :copy
816
+ when Envelope::Local then :local
817
+ when Envelope::Move then :move
818
+ else @manager.mode
819
+ end
820
+ end
821
+
822
+ def identity_storage_key?(key)
823
+ key.instance_of?(MembershipKey) && key.identity?
824
+ end
825
+
826
+ def copy_map_options(other)
827
+ other.respond_to?(:scope) ? { scope: other.scope } : {}
828
+ end
829
+
830
+ def validate_set_like(other)
831
+ return if set_like?(other)
832
+ raise ArgumentError, "value must be a set"
833
+ end
834
+
835
+ def set_like?(object) = object.is_a?(Abstract::Set) || object.is_a?(::Set)
836
+
837
+ def comparison_compatible?(other)
838
+ (!other.respond_to?(:compare_by_identity?) || compare_by_identity? == other.compare_by_identity?) &&
839
+ ordered? == other.is_a?(Abstract::SortedSet)
840
+ end
841
+
842
+ def relation_index(other)
843
+ return other.equality_index if other.is_a?(Abstract::Set)
844
+ membership_index(other)
845
+ end
846
+
847
+ def comparison_key_for_canonical(element)
848
+ return element unless identity_membership?(element)
849
+ token = IDENTITY_TOKENS[element]
850
+ return MembershipKey.new(token, identity: true) if token
851
+ return MembershipKey.new(element, identity: true) if Ractor.shareable?(element)
852
+
853
+ token = IDENTITY_TOKENS.store_if_absent(element) { Object.new.freeze }
854
+ MembershipKey.new(token, identity: true)
855
+ end
856
+
857
+ def flatten_into(source, target, seen)
858
+ raise ArgumentError, "tried to flatten recursive set" if seen.include?(source)
859
+ seen.add(source)
860
+ entered = true
861
+ if source.is_a?(Abstract::Set)
862
+ source.each_stored do |key, value|
863
+ element = source.public_stored(key, value)
864
+ if set_like?(element)
865
+ flatten_into(element, target, seen)
866
+ elsif target.canonical_compatible?(source)
867
+ target.add_stored(key, value)
868
+ else
869
+ target.add(element)
870
+ end
871
+ end
872
+ else
873
+ source.each do |element|
874
+ set_like?(element) ? flatten_into(element, target, seen) : target.add(element)
875
+ end
876
+ end
877
+ ensure
878
+ seen.delete(source) if entered
879
+ end
880
+
881
+ def divide_by_relation(&relation)
882
+ entries = @map.to_a
883
+ graph = {}
884
+ graph.compare_by_identity if compare_by_identity? && !value_modes?
885
+ entries.each do |key, value|
886
+ element = public_stored(key, value)
887
+ graph[key] = entries.filter_map do |candidate_key, candidate_value|
888
+ candidate = public_stored(candidate_key, candidate_value)
889
+ candidate_key if relation.call(element, candidate)
890
+ end
891
+ end
892
+ graph.extend(TSort)
893
+ graph.define_singleton_method(:tsort_each_node) { |&yield_node| each_key(&yield_node) }
894
+ graph.define_singleton_method(:tsort_each_child) { |node, &yield_child| fetch(node).each(&yield_child) }
895
+ graph.strongly_connected_components.map do |component|
896
+ empty_copy.tap do |group|
897
+ component.each { |key| group.add_stored(key, @map.fetch(key)) }
898
+ end
899
+ end
900
+ end
901
+
902
+ # simplecov:disable
903
+ def new_map(...)
904
+ raise "subclass failed to implement #new_map" unless instance_of?(Set)
905
+ raise NoMethodError, "Farce::Abstract::Set should not be instantiated directly. Use a subclass instead."
906
+ end
907
+ # simplecov:enable
908
+ end
909
+ end
910
+ end