xot 0.3.11 → 0.3.13

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d39e1f35513ab0d63cdf20e907d710f2ee955ede52fa2734b4ec8e1e2f129b0c
4
- data.tar.gz: 8e998e8a7ab8afd2450c1c1c52aec4640466e9c73ea0a41e28778af065274bbf
3
+ metadata.gz: 9b42a573c90d9544aae23f3fe0768c88205bea55ca9279b68e794fd6067d7671
4
+ data.tar.gz: 4f3c4072b4c446d4b2afa46226df1f8e2101717951f274913b4a2ed81ae8dc39
5
5
  SHA512:
6
- metadata.gz: ba04a304d6e2ff49e0af62102235720bd3057a4e7545a022de7dfdcfd8bff747a77d77aa9171dfe6cf02adc7b774e57adaaca31b2a5cdc78f824644b59d64175
7
- data.tar.gz: 6a280e45f3bdf1fef6188a88097ff00de70bb1bc9900e3a159003ca232b06066b0d626fde1490084145d7dc65320755df62c30e3c657602f5962e657d0aa09b4
6
+ metadata.gz: 0c98011d4d8e0b8a8f31365af23d52ce1f170abacd1edbc8b704cbf098da6e4f174f41e24a54d819e7816499fd801c9a09ef33283cefaad127e443fff9cacb0c
7
+ data.tar.gz: 1c73c446d67a45360c08b4db4f6de67a5330d1d320f4cc8fd5badf5f1758391b35dbbb2d74e49c529dacbf3c876c438e787153827630f3c6e29b5da409383eb0
@@ -4,6 +4,9 @@ on:
4
4
  push:
5
5
  tags: ['v[0-9]*']
6
6
 
7
+ permissions:
8
+ contents: write
9
+
7
10
  jobs:
8
11
  release:
9
12
  runs-on: macos-latest
@@ -33,23 +36,9 @@ jobs:
33
36
  echo path=$(ruby -e 'print Dir.glob("*.gem").first') >> $GITHUB_OUTPUT
34
37
 
35
38
  - name: create github release
36
- id: release
37
- uses: actions/create-release@v1
38
39
  env:
39
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
40
- with:
41
- tag_name: ${{ github.ref }}
42
- release_name: ${{ github.ref }}
43
-
44
- - name: upload to github release
45
- uses: actions/upload-release-asset@v1
46
- env:
47
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
48
- with:
49
- upload_url: ${{ steps.release.outputs.upload_url }}
50
- asset_path: ./${{ steps.gem.outputs.path }}
51
- asset_name: ${{ steps.gem.outputs.path }}
52
- asset_content_type: application/zip
40
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41
+ run: ruby -I.github/workflows -rutils -e 'release(*ARGV)' ./${{ steps.gem.outputs.path }}
53
42
 
54
43
  - name: upload to rubygems
55
44
  env:
@@ -1,3 +1,9 @@
1
+ require 'shellwords'
2
+
3
+ ALL_REPO = 'xord/all'
4
+ ALL_DIR = '../all'
5
+ ALL_FETCH_DEPTH = 100
6
+
1
7
  RENAMES = {reflex: 'reflexion'}
2
8
 
3
9
  def sh(cmd)
@@ -5,7 +11,7 @@ def sh(cmd)
5
11
  system cmd
6
12
  end
7
13
 
8
- def setup_dependencies(build: true, only: nil)
14
+ def setup_dependencies(only: nil)
9
15
  gemspec_path = `git ls-files`.lines(chomp: true).find {|l| l =~ /\.gemspec$/}
10
16
  return unless gemspec_path
11
17
 
@@ -13,44 +19,109 @@ def setup_dependencies(build: true, only: nil)
13
19
  name = File.basename gemspec_path, '.gemspec'
14
20
 
15
21
  exts = File.readlines('Rakefile')
16
- .map {|l| l[%r|^\s*require\W+(\w+)/extension\W+$|, 1]}
22
+ .map {|l| l[%r|^\s*require\W+([\w\-\_]+)/extension\W+$|, 1]}
17
23
  .compact
18
24
  .reject {|ext| ext == name}
19
25
  exts = exts & [only].flatten.map(&:to_s) if only
26
+ return if exts.empty?
27
+
28
+ unless setup_dependencies_via_monorepo(exts)
29
+ setup_dependencies_via_each_repo_by_version(gemspec, exts)
30
+ end
31
+
32
+ exts.each {|ext| sh %( cd ../#{ext} && rake ext )}
33
+ end
34
+
35
+ def setup_dependencies_via_monorepo(exts)
36
+ return false unless checkout_monorepo
37
+ exts.each {|ext| sh %( ln -snf all/#{ext} ../#{ext} )}
38
+ true
39
+ end
40
+
41
+ def checkout_monorepo()
42
+ uuid = `git log -1 --format=%B`[/^\[\[([0-9a-fA-F-]+)\]\]$/, 1]
43
+ return false unless uuid
44
+
45
+ commit = setup_monorepo uuid
46
+ return false unless commit
47
+
48
+ Dir.chdir(ALL_DIR) {sh %( git checkout -q #{commit} )}
49
+ true
50
+ end
51
+
52
+ def setup_monorepo(uuid)
53
+ unless File.directory? ALL_DIR
54
+ url = "https://github.com/#{ALL_REPO}.git"
55
+ sh %( git clone --no-tags --depth #{ALL_FETCH_DEPTH} #{url} #{ALL_DIR} )
56
+ end
57
+ loop do
58
+ commit = find_monorepo_commit uuid
59
+ return commit if commit
60
+
61
+ deepened = Dir.chdir ALL_DIR do
62
+ before = `git rev-list --count HEAD`.to_i
63
+ sh %( git fetch --deepen #{ALL_FETCH_DEPTH} )
64
+ `git rev-list --count HEAD`.to_i > before
65
+ end
66
+ return nil unless deepened
67
+ end
68
+ end
69
+
70
+ def find_monorepo_commit(uuid)
71
+ Dir.chdir ALL_DIR do
72
+ out = `git log origin/HEAD -F --grep="[[#{uuid}]]" --format=%H -1`.strip
73
+ out.empty? ? nil : out
74
+ end
75
+ end
20
76
 
77
+ def setup_dependencies_via_each_repo_by_version(gemspec, exts)
21
78
  exts.each do |ext|
22
79
  gem = RENAMES[ext.to_sym].then {|s| s || ext}
23
- ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*>=\s*([\d\.]+)\s*['"]/, 1]
80
+ ver = gemspec[/add_dependency.*['"]#{gem}['"].*['"]\s*~>\s*([\d\.]+)\s*['"]/, 1]
24
81
  opts = '-c advice.detachedHead=false --depth 1'
25
82
  clone = "git clone #{opts} https://github.com/xord/#{ext}.git ../#{ext}"
26
83
 
27
84
  # 'rake subtree:push' pushes all subrepos, so cloning by new tag
28
85
  # often fails before tagging each new tag
29
86
  sh %( #{clone} --branch v#{ver} || #{clone} )
30
- sh %( cd ../#{ext} && rake ext )
31
87
  end
32
88
  end
33
89
 
34
90
  def tag_versions()
35
- tags = `git tag`.lines chomp: true
36
- vers = `git log --oneline ./VERSION`
91
+ changes = changelogs
92
+ tags = `git tag`.lines chomp: true
93
+ vers = `git log --oneline ./VERSION`
37
94
  .lines(chomp: true)
38
95
  .map {|line| line.split.first[/^\w+$/]}
39
- .map {|hash| [`git cat-file -p #{hash}:./VERSION 2>/dev/null`[/[\d\.]+/], hash]}
40
- .select {|ver, hash| ver && hash}
96
+ .map {|sha| [`git cat-file -p #{sha}:./VERSION 2>/dev/null`[/[\d\.]+/], sha]}
97
+ .select {|ver, sha| ver && sha}
41
98
  .reverse
42
99
  .to_h
43
100
 
44
- changes = File.read('ChangeLog.md')
45
- .split(/^\s*##\s*\[\s*v([\d\.]+)\s*\].*$/)
46
- .slice(1..-1)
47
- .each_slice(2)
48
- .to_h
49
- .transform_values(&:strip!)
50
-
51
- vers.to_a.reverse.each do |ver, hash|
101
+ vers.to_a.reverse.each do |ver, sha|
52
102
  tag = "v#{ver}"
53
103
  break if tags.include?(tag)
54
- sh %( git tag -a -m \"#{changes[ver]&.gsub '"', '\\"'}\" #{tag} #{hash} )
104
+ sh %( git tag -a -m \"#{changes[tag]&.gsub '"', '\\"'}\" #{tag} #{sha} )
55
105
  end
56
106
  end
107
+
108
+ def release(*paths)
109
+ tag = ENV['GITHUB_REF']&.sub(%r|^refs/tags/|, '') || raise('GITHUB_REF tag not set')
110
+ notes = (changelogs[tag] || '').shellescape
111
+ paths = paths.flatten.join ' '
112
+
113
+ sh(%( gh release create #{tag} #{paths} --notes #{notes} )) ||
114
+ sh(%( gh release upload #{tag} #{paths} --clobber )) ||
115
+ raise('failed to upload to releases')
116
+ end
117
+
118
+ def changelogs()
119
+ File.read('ChangeLog.md')
120
+ .split(/^\s*##\s*\[\s*(v[\d\.]+)\s*\].*$/)
121
+ .slice(1..)
122
+ .each_slice(2)
123
+ .to_h
124
+ .transform_values(&:strip!)
125
+ rescue Errno::ENOENT
126
+ raise 'failed to get changelogs'
127
+ end
data/ChangeLog.md CHANGED
@@ -1,6 +1,26 @@
1
1
  # xot ChangeLog
2
2
 
3
3
 
4
+ ## [v0.3.13] - 2026-05-17
5
+
6
+ - Rewrite README.md
7
+ - CI: Migrate release-gem.yml from actions/create-release to gh release create
8
+
9
+
10
+ ## [v0.3.12] - 2026-05-10
11
+
12
+ - Add hint_memory_usage callback for external memory GC integration
13
+ - Add release helper that uploads to GitHub Releases via gh CLI
14
+ - Add Extension.name(downcase) for kebab-case module naming
15
+ - Add lib_name to Extension and use import libraries on Windows
16
+ - Clear DEFFILE in generated Makefile on Windows
17
+ - Resolve dependencies by monorepo commit UUID in CI
18
+ - Remove deprecated has_rdoc= from gemspecs
19
+
20
+ - Skip linking dependency native extensions to avoid duplicate symbols
21
+ - Fix compiler and linker warnings
22
+
23
+
4
24
  ## [v0.3.11] - 2026-04-17
5
25
 
6
26
  - Invert build script default verbosity: quiet by default, add verbose option
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Xot - Some useful utility classes and functions
1
+ # Xot - Shared utility classes and functions for C++ and Ruby
2
2
 
3
3
  [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/xord/xot)
4
4
  ![License](https://img.shields.io/github/license/xord/xot)
@@ -21,20 +21,29 @@ Thanks for your support! 🙌
21
21
 
22
22
  ## 🚀 About
23
23
 
24
- **Xot** is a small utility library that was extracted for internal use to provide basic helper classes and functions across our own Ruby gems.
24
+ **Xot** is the foundational utility layer used by every other library in the `xord/*` family Rucy, Beeps, Rays, Reflex, Processing, RubySketch, and Reight.
25
25
 
26
- It’s not intended for general public use, but rather serves as a simple collection of utilities to make our own development process more efficient and consistent.
26
+ It is split into two layers that ship in a single gem:
27
27
 
28
- Feel free to explore, but please note that it’s not actively maintained or intended for broad adoption.
28
+ - **C++ headers and library** (`include/xot/`, `src/`) building blocks such as reference counting, the pimpl idiom, non-copyable base, plus thin wrappers for strings, time, exceptions, and debug output. These are linked into the native extensions of the other `xord/*` gems.
29
+ - **Ruby helpers** (`lib/xot/`) — small meta-programming mixins (`Hookable`, `Inspectable`, accessor builders, ...), bit-flag utilities, and shared Rake / test scaffolding used by our gems.
30
+
31
+ Xot exists primarily to keep these patterns consistent across our own projects. It is not designed as a general-purpose dependency, and its API is not promised to be stable for outside use. You are welcome to read and learn from it, but pin a specific version if you depend on it directly.
32
+
33
+ ## 📋 Requirements
34
+
35
+ - Ruby **3.0.0** or later
36
+ - A C++ compiler with C++20 support (Clang on macOS / iOS, GCC or MSVC on Linux / Windows)
37
+ - [Rake](https://rubygems.org/gems/rake) and [test-unit](https://rubygems.org/gems/test-unit) (development only)
29
38
 
30
39
  ## 📦 Installation
31
40
 
32
41
  Add this line to your Gemfile:
33
42
  ```ruby
34
- $ gem 'xot'
43
+ gem 'xot'
35
44
  ```
36
45
 
37
- Then, install gem:
46
+ Then install:
38
47
  ```bash
39
48
  $ bundle install
40
49
  ```
@@ -44,6 +53,145 @@ Or install it directly:
44
53
  $ gem install xot
45
54
  ```
46
55
 
56
+ When installed via `gem install`, the C++ headers under `include/xot/` are placed inside the gem directory so that other `xord/*` extensions can locate them at build time (via `Xot::Extension.inc_dir`).
57
+
58
+ ## 📚 What's Included
59
+
60
+ ### C++ headers (`include/xot/`)
61
+
62
+ | Header | Provides |
63
+ | ---------------- | ------------------------------------------------------------------------ |
64
+ | `defs.h` | Type aliases (`uint`, `ushort`, `ulong`, `schar`, `longlong`, ...) |
65
+ | `noncopyable.h` | `Xot::NonCopyable` — base class that disables copy and assignment |
66
+ | `ref.h` | `Xot::RefCountable<>` and `Xot::Ref<T>` — intrusive reference counting |
67
+ | `pimpl.h` | `Xot::PImpl<T>` / `Xot::PSharedImpl<T>` — pimpl idiom on top of smart ptrs |
68
+ | `string.h` | `Xot::String` (extends `std::string`), `stringf`, `split`, `to_s` |
69
+ | `time.h` | `Xot::time()` (seconds since epoch, double), `Xot::sleep(seconds)` |
70
+ | `exception.h` | `XotError` hierarchy and `xot_error` / `argument_error` / ... throw helpers |
71
+ | `debug.h` | `Xot::dout` / `doutln` — printf-style debug output (no-op in release) |
72
+ | `util.h` | Bit / flag helpers, `random`, `deg2rad`, `rad2deg`, memory-usage hints |
73
+ | `windows.h` | Win32 helpers used by other libraries |
74
+
75
+ ### Ruby modules (`lib/xot/`)
76
+
77
+ | Module / class | Purpose |
78
+ | --------------------------- | ----------------------------------------------------------------------- |
79
+ | `Xot::Hookable` | Attach `on_*` hook methods, or `before` / `after` wrappers around an existing method, on a single object |
80
+ | `Xot::Inspectable` | Compact default `inspect` (class + object_id only) — safe under circular references |
81
+ | `Xot::BitFlag` | Build symbolic bit-flag sets and convert between symbols and bitmasks |
82
+ | `Xot::BitFlagAccessor` | Class-level accessor generator backed by a `BitFlag` |
83
+ | `Xot::BitUtil` | Bit manipulation helpers (`bit(n)`, etc.) |
84
+ | `Xot::BlockUtil` | `instance_eval`-or-block-call dispatching |
85
+ | `Xot::ConstSymbolAccessor` | Define accessors that translate symbols to module constants |
86
+ | `Xot::UniversalAccessor` | Single-method getter / setter (`obj.x` reads, `obj.x value` writes) |
87
+ | `Xot::Setter` | Bulk attribute setter mixin |
88
+ | `Xot::Invoker` | Helper for invoking methods / blocks safely |
89
+ | `Xot::Util` | Misc Ruby utilities |
90
+ | `Xot::Extension` | Path / name / version helpers used by every `xord/*` gem's build script |
91
+ | `Xot::Rake` | Rake DSL (`default_tasks`, `build_native_library`, `build_ruby_extension`, `test_ruby_extension`, `build_ruby_gem`, ...) |
92
+ | `Xot::Test` | Test-unit helpers |
93
+
94
+ ## 💡 Usage
95
+
96
+ ### C++ — reference counting
97
+
98
+ ```cpp
99
+ #include <xot/ref.h>
100
+
101
+ class Thing : public Xot::RefCountable<>
102
+ {
103
+ protected:
104
+ ~Thing () = default; // destructor is protected; Ref<> handles deletion
105
+ };
106
+
107
+ Xot::Ref<Thing> a = new Thing; // refcount = 1
108
+ {
109
+ Xot::Ref<Thing> b = a; // refcount = 2
110
+ } // refcount = 1
111
+ // when `a` goes out of scope, refcount = 0 and the object is deleted
112
+ ```
113
+
114
+ ### C++ — pimpl
115
+
116
+ ```cpp
117
+ #include <xot/pimpl.h>
118
+
119
+ // in the header
120
+ class Widget
121
+ {
122
+ public:
123
+ Widget ();
124
+ int value () const;
125
+ private:
126
+ struct Data;
127
+ Xot::PImpl<Data> self;
128
+ };
129
+
130
+ // in the .cpp
131
+ struct Widget::Data { int n = 42; };
132
+ Widget::Widget () {}
133
+ int Widget::value () const { return self->n; }
134
+ ```
135
+
136
+ ### Ruby — `BitFlag`
137
+
138
+ ```ruby
139
+ require 'xot/bit_flag'
140
+
141
+ flags = Xot::BitFlag.new(read: 0x1, write: 0x2, exec: 0x4)
142
+ mask = flags.symbols2bits(:read, :exec) # => 5
143
+ flags.bits2symbols(mask) # => [:read, :exec]
144
+ ```
145
+
146
+ ### Ruby — `Hookable`
147
+
148
+ ```ruby
149
+ require 'xot/hookable'
150
+
151
+ class Greeter
152
+ include Xot::Hookable
153
+ def greet(name) = "hello, #{name}"
154
+ end
155
+
156
+ g = Greeter.new
157
+ g.before(:greet) {|name| puts "about to greet #{name}" }
158
+ g.greet 'world'
159
+ # => about to greet world
160
+ # => "hello, world"
161
+
162
+ # `on` creates a brand-new on_* method instead of wrapping an existing one
163
+ g.on(:click) {|x, y| puts "clicked at #{x}, #{y}" }
164
+ g.on_click 1, 2
165
+ ```
166
+
167
+ ### Ruby — `UniversalAccessor`
168
+
169
+ ```ruby
170
+ require 'xot/universal_accessor'
171
+
172
+ class Box
173
+ attr_accessor :width
174
+ universal_accessor :width
175
+ end
176
+
177
+ box = Box.new
178
+ box.width 10 # writes
179
+ box.width # => 10 (reads)
180
+ ```
181
+
182
+ ## 🛠️ Development
183
+
184
+ Xot uses a shared Rakefile pattern. From the gem root:
185
+
186
+ ```bash
187
+ $ rake lib # build the native C++ library (libxot)
188
+ $ rake ext # build the Ruby C extension
189
+ $ rake test # run the test suite (test/test_*.rb)
190
+ $ rake # default: builds the extension
191
+ ```
192
+
193
+ In the [`xord/all`](https://github.com/xord/all) monorepo you can also scope by module, e.g. `rake xot test`.
194
+
47
195
  ## 📜 License
48
196
 
49
197
  **Xot** is licensed under the MIT License.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.11
1
+ 0.3.13
data/include/xot/util.h CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
 
7
7
  #include <stdint.h>
8
+ #include <sys/types.h>
8
9
  #include <math.h>
9
10
  #include <assert.h>
10
11
  #include <xot/defs.h>
@@ -124,6 +125,13 @@ namespace Xot
124
125
  }
125
126
 
126
127
 
128
+ typedef void (*HintMemoryUsageFun)(ssize_t);
129
+
130
+ void set_hint_memory_usage_fun (HintMemoryUsageFun fun);
131
+
132
+ void hint_memory_usage (ssize_t size);
133
+
134
+
127
135
  #if defined(OSX) || defined(IOS)
128
136
 
129
137
  void safe_cfrelease (const void* ref);
data/include/xot.h CHANGED
@@ -1,7 +1,7 @@
1
1
  // -*- c++ -*-
2
2
  #pragma once
3
- #ifndef __XOT_H__
4
- #define __XOT_H__
3
+ #ifndef __XOT_ALL_H__
4
+ #define __XOT_ALL_H__
5
5
 
6
6
 
7
7
  #include <xot/defs.h>
data/lib/xot/extconf.rb CHANGED
@@ -11,8 +11,6 @@ module Xot
11
11
  include Xot::Rake
12
12
  include Xot::Util
13
13
 
14
- attr_reader :extensions, :defs, :inc_dirs, :lib_dirs, :headers, :libs, :local_libs, :frameworks
15
-
16
14
  def initialize(*extensions, &block)
17
15
  @extensions = extensions.map {|x| x.const_get :Extension}
18
16
  @defs, @inc_dirs, @lib_dirs, @headers, @libs, @local_libs, @frameworks =
@@ -20,6 +18,12 @@ module Xot
20
18
  Xot::BlockUtil.instance_eval_or_block_call self, &block if block
21
19
  end
22
20
 
21
+ attr_reader :extensions, :defs, :inc_dirs, :lib_dirs, :headers, :libs, :local_libs, :frameworks
22
+
23
+ def my_ext()
24
+ extensions.last
25
+ end
26
+
23
27
  def debug()
24
28
  env :DEBUG, false
25
29
  end
@@ -28,19 +32,23 @@ module Xot
28
32
  yield if block_given?
29
33
 
30
34
  extensions.each do |ext|
31
- name = ext.name.downcase
32
- headers << "#{name}.h"
33
- local_libs << name
35
+ name = ext.name true
36
+ lib_name = ext == my_ext ? name : ext.lib_name
37
+ headers << "#{name}.h"
38
+ local_libs << lib_name if lib_name
34
39
  end
35
40
 
36
41
  ldflags = $LDFLAGS.dup
37
- if osx?
42
+ case
43
+ when osx?
38
44
  opt = '-Wl,-undefined,dynamic_lookup'
39
45
  ldflags << " #{opt}" unless ($DLDFLAGS || '').include?(opt)
40
46
  ldflags << ' -Wl,-bind_at_load' if osx? && debug?
47
+ when wasm?
48
+ build_lib_objs_for_wasm
41
49
  end
42
50
 
43
- local_libs << (clang? ? 'c++' : 'stdc++')
51
+ local_libs << 'stdc++' if gcc?
44
52
 
45
53
  $CPPFLAGS = make_cppflags $CPPFLAGS, defs, inc_dirs
46
54
  $CFLAGS = make_cflags $CFLAGS + ' -x c++'
@@ -51,13 +59,62 @@ module Xot
51
59
 
52
60
  def create_makefile(*args)
53
61
  extensions.each do |ext|
54
- dir_config ext.name.downcase, ext.inc_dir, ext.lib_dir
62
+ dir_config ext.name(true), ext.inc_dir, ext.lib_dir
55
63
  end
56
64
 
57
65
  exit 1 unless headers.all? {|s| have_header s}
58
- exit 1 unless libs.all? {|s| have_library s, 't'}
66
+ exit 1 unless libs.all? {|s| have_library s, 't'} unless wasm?
59
67
 
60
68
  super
69
+
70
+ export_all_symbols if mingw? || cygwin?
71
+ link_lib_objs_for_wasm if wasm?
72
+ end
73
+
74
+ def export_all_symbols()
75
+ name = my_ext.name true
76
+ opts = %W[
77
+ -Wl,--export-all-symbols,--whole-archive
78
+ -l#{name}
79
+ -Wl,--no-whole-archive
80
+ ].join ' '
81
+ filter_file('Makefile') {|s|
82
+ s.sub(/^DEFFILE\s*=.*$/, 'DEFFILE =')
83
+ .sub(/^(LOCAL_LIBS\s*=.*) -l#{name}\b/) {"#{$1} #{opts}"}
84
+ }
85
+ end
86
+
87
+ def build_lib_objs_for_wasm()
88
+ ruby_dirs = [
89
+ "#{ENV['extout']}/include/wasm32-emscripten",
90
+ "#{ENV['top_srcdir']}/include"
91
+ ]
92
+ envs = {
93
+ CC: '',
94
+ CXX: '',
95
+ AR: '',
96
+ RANLIB: '',
97
+ CPPFLAGS: ' -sUSE_SDL=2 -sUSE_SDL_TTF=2',
98
+ CFLAGS: ' -sUSE_SDL=2 -sUSE_SDL_TTF=2',
99
+ CXXFLAGS: '',
100
+ LDFLAGS: ' -sUSE_SDL=2 -sUSE_SDL_TTF=2',
101
+ INCDIRS: ruby_dirs.join(' ')
102
+ }.map {|k, v| "#{k}='#{(RbConfig::CONFIG[k.to_s] || '') + v}'"}
103
+
104
+ Dir.chdir target.root_dir do
105
+ cmd = "#{envs.join ' '} rake ext:lib_objs"
106
+ puts cmd
107
+ system cmd
108
+ end
109
+ end
110
+
111
+ def link_lib_objs_for_wasm()
112
+ lib_objs = Dir.glob "#{target.ext_dir}/**/__libobj_*.o"
113
+ filter_file 'Makefile' do |str|
114
+ str.sub(/^(\s*)(.*\$\(AR\).*)$/) {
115
+ "#{$1}#{$2}\n#{$1}$(Q) $(AR) r $@ #{lib_objs.join ' '}"
116
+ }
117
+ end
61
118
  end
62
119
 
63
120
  end# ExtConf
data/lib/xot/extension.rb CHANGED
@@ -5,8 +5,10 @@ module Xot
5
5
 
6
6
  module_function
7
7
 
8
- def name()
9
- super.split('::')[-2]
8
+ def name(downcase = false)
9
+ super().split('::')[-2].then {|s|
10
+ downcase ? s.gsub(/([a-z])([A-Z])/) {"#{$1}-#{$2}"}.downcase : s
11
+ }
10
12
  end
11
13
 
12
14
  def version()
@@ -29,6 +31,10 @@ module Xot
29
31
  root_dir 'ext'
30
32
  end
31
33
 
34
+ def lib_name()
35
+ name true
36
+ end
37
+
32
38
  end# Extension
33
39
 
34
40
 
data/lib/xot/rake/util.rb CHANGED
@@ -19,7 +19,7 @@ module Xot
19
19
  end
20
20
 
21
21
  def target_name()
22
- get_env :EXTNAME, target.name.downcase
22
+ get_env :EXTNAME, target.name(true)
23
23
  end
24
24
 
25
25
  def inc_dir()
@@ -81,7 +81,7 @@ module Xot
81
81
  paths.reject! {|path| path =~ %r(/osx/)} unless osx?
82
82
  paths.reject! {|path| path =~ %r(/ios/)} unless ios?
83
83
  paths.reject! {|path| path =~ %r(/win32/)} unless win32?
84
- paths.reject! {|path| path =~ %r(/sdl/)} unless linux?
84
+ paths.reject! {|path| path =~ %r(/sdl/)} unless linux? || wasm?
85
85
  make_path_map paths, src_ext_map
86
86
  end
87
87
 
@@ -186,12 +186,13 @@ module Xot
186
186
  def make_cppflags_defs(defs = [])
187
187
  a = defs.dup
188
188
  a << (debug? ? '_DEBUG' : 'NDEBUG')
189
- a << target.name.upcase
189
+ a << target_name.gsub('-', '_').upcase
190
190
  a << $~[0].upcase if RUBY_PLATFORM =~ /mswin|mingw|cygwin|darwin/i
191
191
  a << 'WIN32' if win32?
192
192
  a << 'OSX' if osx?
193
193
  a << 'IOS' if ios?
194
194
  a << 'LINUX' if linux?
195
+ a << 'WASM' if wasm?
195
196
  a << 'GCC' if gcc?
196
197
  a << 'CLANG' if clang?
197
198
  a << '_USE_MATH_DEFINES' if gcc?
@@ -221,7 +222,7 @@ module Xot
221
222
  s = flags.dup
222
223
  s << warning_opts.map {|s| " -W#{s}"}.join
223
224
  s << " -arch arm64" if osx? && arm64?
224
- s << ' -std=c++20' if gcc?
225
+ s << ' -std=c++20' if gcc? || emcc?
225
226
  s << ' -std=c++20 -stdlib=libc++ -mmacosx-version-min=10.10' if clang?
226
227
  s << ' ' + RbConfig::CONFIG['debugflags'] if debug?
227
228
  s.gsub!(/-O\d?\w*/, '-O0') if debug?
data/lib/xot/rake.rb CHANGED
@@ -104,7 +104,7 @@ module Xot
104
104
  end
105
105
 
106
106
  def build_ruby_extension(dlname: nil, dlext: nil, liboutput: true)
107
- dlname = get_env :DLNAME, dlname || "#{target_name}_ext"
107
+ dlname = get_env :DLNAME, dlname || "#{target_name.gsub('-', '_')}_ext"
108
108
  dlext = get_env :DLEXT, dlext || RbConfig::CONFIG['DLEXT'] || 'so'
109
109
 
110
110
  extconf = File.join ext_dir, 'extconf.rb'
@@ -116,7 +116,7 @@ module Xot
116
116
  libout = File.join lib_dir, outname
117
117
 
118
118
  srcs = FileList["#{ext_dir}/**/*.cpp"]
119
- libs = extensions.map {|x| "#{x.lib_dir}/lib#{x.name.downcase}.a"}
119
+ libs = extensions.map {|x| "#{x.lib_dir}/lib#{x.name(true)}.a"}
120
120
 
121
121
  alias_task :ext => (liboutput ? libout : extout)
122
122
  alias_task :clean => 'ext:clean'
@@ -126,7 +126,7 @@ module Xot
126
126
  desc "build #{libout}"
127
127
  file libout => extout do
128
128
  libdir = File.dirname libout
129
- libimp = extout.sub /\.#{dlext}$/, '.dll.a'
129
+ libimp = File.join File.dirname(extout), "lib#{target_name}.dll.a"
130
130
  sh %( cp #{extout} #{libdir} )
131
131
  sh %( cp #{libimp} #{libdir} ) if mingw? || cygwin?
132
132
  end
@@ -150,9 +150,17 @@ module Xot
150
150
  sh %( cd #{ext_dir} && #{cxx} -M #{cppflags} #{inc} #{src} > #{dep} )
151
151
  end
152
152
 
153
+ desc "compile src/**/*"
154
+ task :lib_objs => :lib do
155
+ (srcs_map.values + vendor_srcs_map.values).each do |obj|
156
+ to = File.join ext_dir, "__libobj_#{obj.gsub '/', '_'}"
157
+ sh %( cp #{obj} #{to} )
158
+ end
159
+ end
160
+
153
161
  task :clean do
154
162
  sh %( cd #{ext_dir} && make clean ) if File.exist? makefile
155
- sh %( rm -rf #{libout} )
163
+ sh %( rm -rf #{libout} #{ext_dir}/__libobj_*.o )
156
164
  end
157
165
 
158
166
  task :clobber do
@@ -297,24 +305,25 @@ module Xot
297
305
  file dir do
298
306
  rake_puts "vendoring #{name}"
299
307
  q = ::Rake.verbose ? '' : '-q'
300
- opts = "#{q} -c advice.detachedHead=false --depth 1"
308
+ opts = "#{q} -c advice.detachedHead=false --depth 1 --no-tags"
301
309
  opts += " --branch #{branch || tag}" if branch || tag
302
- opts += " --recursive" if submodules.empty?
303
310
  sh %( git clone #{opts} #{repos} #{dir} )
304
311
  Dir.chdir dir do
305
- sh %( git fetch #{q} --depth 1 origin #{commit} )
306
- sh %( git checkout #{q} #{commit} )
307
- end if commit
308
- unless submodules.empty?
309
- Dir.chdir dir do
310
- submodules.each do |path|
311
- sh %( git submodule #{q} init #{path} )
312
- end
313
- sh %( git submodule #{q} update --depth=1 )
312
+ if commit
313
+ sh %( git fetch #{q} --depth 1 origin #{commit} )
314
+ sh %( git checkout #{q} #{commit} )
315
+ end
316
+
317
+ if submodules.empty?
318
+ sh %( git submodule #{q} update --init --recursive --depth 1 )
319
+ else
320
+ submodules.each {|path| sh %( git submodule #{q} init #{path} )}
321
+ sh %( git submodule #{q} update --depth 1 )
314
322
  sh post_submodules if post_submodules
315
323
  end
324
+
325
+ after_clone_block.call if after_clone_block
316
326
  end
317
- Dir.chdir(dir) {after_clone_block.call} if after_clone_block
318
327
  unless get_env :VENDOR_NOCOMPILE, false
319
328
  vendor_srcs_map(*srcdirs).each do |src, obj|
320
329
  rake_puts "compiling #{src}"
data/lib/xot/util.rb CHANGED
@@ -65,7 +65,7 @@ module Xot
65
65
  end
66
66
 
67
67
  def osx?()
68
- /darwin/.match? RUBY_PLATFORM
68
+ !wasm? && /darwin/.match?(RUBY_PLATFORM)
69
69
  end
70
70
 
71
71
  def ios?()
@@ -73,23 +73,27 @@ module Xot
73
73
  end
74
74
 
75
75
  def win32?()
76
- /mswin|ming|cygwin/.match? RUBY_PLATFORM
76
+ !wasm? && /mswin|ming|cygwin/.match?(RUBY_PLATFORM)
77
77
  end
78
78
 
79
79
  def mswin?()
80
- /mswin/.match? RUBY_PLATFORM
80
+ !wasm? && /mswin/.match?(RUBY_PLATFORM)
81
81
  end
82
82
 
83
83
  def mingw?()
84
- /ming/.match? RUBY_PLATFORM
84
+ !wasm? && /ming/.match?(RUBY_PLATFORM)
85
85
  end
86
86
 
87
87
  def cygwin?()
88
- /cygwin/.match? RUBY_PLATFORM
88
+ !wasm? && /cygwin/.match?(RUBY_PLATFORM)
89
89
  end
90
90
 
91
91
  def linux?()
92
- /linux/.match? RUBY_PLATFORM
92
+ !wasm? && /linux/.match?(RUBY_PLATFORM)
93
+ end
94
+
95
+ def wasm?()
96
+ emcc?
93
97
  end
94
98
 
95
99
  def gcc?()
@@ -100,6 +104,10 @@ module Xot
100
104
  /(^|\-)clang/i.match? cc
101
105
  end
102
106
 
107
+ def emcc?()
108
+ /(^|\-)emcc$/i.match? cc
109
+ end
110
+
103
111
  def github_actions?()
104
112
  ENV['GITHUB_ACTIONS'] == 'true'
105
113
  end
data/src/util.cpp CHANGED
@@ -53,6 +53,22 @@ namespace Xot
53
53
  }
54
54
 
55
55
 
56
+ static HintMemoryUsageFun hint_memory_usage_fun = NULL;
57
+
58
+ void
59
+ set_hint_memory_usage_fun (HintMemoryUsageFun fun)
60
+ {
61
+ hint_memory_usage_fun = fun;
62
+ }
63
+
64
+ void
65
+ hint_memory_usage (ssize_t size)
66
+ {
67
+ if (hint_memory_usage_fun && size != 0)
68
+ hint_memory_usage_fun(size);
69
+ }
70
+
71
+
56
72
  #if defined(OSX) || defined(IOS)
57
73
 
58
74
  void
data/xot.gemspec CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
10
10
  end
11
11
 
12
12
  ext = Xot::Extension
13
- name = ext.name.downcase
13
+ name = ext.name true
14
14
  rdocs = glob.call *%w[README]
15
15
 
16
16
  s.name = name
@@ -29,7 +29,6 @@ Gem::Specification.new do |s|
29
29
  s.executables = s.files.grep(%r{^bin/}) {|f| File.basename f}
30
30
  s.test_files = s.files.grep %r{^(test|spec|features)/}
31
31
  s.extra_rdoc_files = rdocs.to_a
32
- s.has_rdoc = true
33
32
 
34
33
  s.extensions << 'Rakefile'
35
34
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.11
4
+ version: 0.3.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - xordog
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-17 00:00:00.000000000 Z
11
+ date: 2026-05-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: This library include some useful utility classes and functions for development
14
14
  with C++.