ruby-sfml 3.0.0.4 → 3.0.0.5
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 +4 -4
- data/CHANGELOG.md +99 -0
- data/lib/sfml/audio/music.rb +14 -0
- data/lib/sfml/audio/sound_buffer.rb +13 -0
- data/lib/sfml/audio/sound_recorder.rb +126 -13
- data/lib/sfml/audio/sound_stream.rb +103 -0
- data/lib/sfml/c/audio.rb +47 -0
- data/lib/sfml/c/graphics.rb +146 -4
- data/lib/sfml/c/network.rb +45 -5
- data/lib/sfml/c/system.rb +12 -0
- data/lib/sfml/c/window.rb +20 -2
- data/lib/sfml/graphics/circle_shape.rb +3 -0
- data/lib/sfml/graphics/color.rb +30 -0
- data/lib/sfml/graphics/convex_shape.rb +3 -0
- data/lib/sfml/graphics/font.rb +19 -0
- data/lib/sfml/graphics/image.rb +12 -0
- data/lib/sfml/graphics/rectangle_shape.rb +5 -0
- data/lib/sfml/graphics/shader.rb +86 -0
- data/lib/sfml/graphics/shape.rb +114 -0
- data/lib/sfml/graphics/shape_inspectable.rb +92 -0
- data/lib/sfml/graphics/texture.rb +46 -10
- data/lib/sfml/graphics/transformable_object.rb +48 -0
- data/lib/sfml/graphics/vertex_array.rb +12 -0
- data/lib/sfml/graphics/vertex_buffer.rb +12 -0
- data/lib/sfml/network/packet.rb +123 -0
- data/lib/sfml/network/tcp_socket.rb +19 -0
- data/lib/sfml/network/udp_socket.rb +23 -0
- data/lib/sfml/system/input_stream.rb +88 -0
- data/lib/sfml/version.rb +1 -1
- data/lib/sfml/window/context.rb +56 -0
- data/lib/sfml/window/keyboard.rb +92 -4
- data/lib/sfml/window/video_mode.rb +22 -0
- data/lib/sfml.rb +6 -0
- metadata +7 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Abstract callback-driven shape — for when neither CircleShape,
|
|
3
|
+
# RectangleShape, nor ConvexShape fits. Subclass and override
|
|
4
|
+
# `#point_count` (returns Integer) and `#point(i)` (returns a
|
|
5
|
+
# Vector2 or `[x, y]`). CSFML asks Ruby every frame for the point
|
|
6
|
+
# list, so you can drive geometry from live data.
|
|
7
|
+
#
|
|
8
|
+
# class Pentagon < SFML::Shape
|
|
9
|
+
# def point_count = 5
|
|
10
|
+
# def point(i)
|
|
11
|
+
# angle = i * 2 * Math::PI / 5 - Math::PI / 2
|
|
12
|
+
# [Math.cos(angle) * 50, Math.sin(angle) * 50]
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# pent = Pentagon.new(fill_color: SFML::Color.red, position: [400, 300])
|
|
17
|
+
# window.draw(pent)
|
|
18
|
+
#
|
|
19
|
+
# When your underlying geometry changes (e.g. the data feeding
|
|
20
|
+
# `#point` updates), call `#update` to invalidate the cached
|
|
21
|
+
# outline / bounds — otherwise the next draw will still use the
|
|
22
|
+
# previously sampled points.
|
|
23
|
+
#
|
|
24
|
+
# CAVEATS
|
|
25
|
+
# * Callbacks run on whichever thread invokes the draw. With
|
|
26
|
+
# single-threaded rendering (the SFML default) this is fine; if
|
|
27
|
+
# you're driving the renderer from a different thread, make sure
|
|
28
|
+
# `#point_count` / `#point` are thread-safe.
|
|
29
|
+
# * Keep a Ruby reference to the Shape — if it's GC'd while CSFML
|
|
30
|
+
# still holds the function pointers, the next draw crashes.
|
|
31
|
+
class Shape
|
|
32
|
+
include Graphics::Transformable
|
|
33
|
+
include Graphics::ShapeInspectable
|
|
34
|
+
CSFML_PREFIX = :sfShape
|
|
35
|
+
|
|
36
|
+
def initialize(**opts)
|
|
37
|
+
# Hold strong refs so the GC doesn't disappear our FFI callbacks.
|
|
38
|
+
@get_point_count_cb = FFI::Function.new(:size_t, [:pointer]) do |_user|
|
|
39
|
+
Integer(point_count)
|
|
40
|
+
end
|
|
41
|
+
@get_point_cb = FFI::Function.new(C::System::Vector2f.by_value, [:size_t, :pointer]) do |i, _user|
|
|
42
|
+
p = point(i)
|
|
43
|
+
pt = p.is_a?(Vector2) ? p : Vector2.new(*p)
|
|
44
|
+
v = C::System::Vector2f.new
|
|
45
|
+
v[:x] = pt.x.to_f; v[:y] = pt.y.to_f
|
|
46
|
+
v
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
ptr = C::Graphics.sfShape_create(@get_point_count_cb, @get_point_cb, nil)
|
|
50
|
+
raise Error, "sfShape_create returned NULL" if ptr.null?
|
|
51
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfShape_destroy))
|
|
52
|
+
|
|
53
|
+
self.fill_color = opts[:fill_color] if opts.key?(:fill_color)
|
|
54
|
+
self.outline_color = opts[:outline_color] if opts.key?(:outline_color)
|
|
55
|
+
self.outline_thickness = opts[:outline_thickness] if opts.key?(:outline_thickness)
|
|
56
|
+
self.texture = opts[:texture] if opts.key?(:texture)
|
|
57
|
+
self.texture_rect = opts[:texture_rect] if opts.key?(:texture_rect)
|
|
58
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
59
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
60
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
61
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# ---- Subclass hooks ----
|
|
65
|
+
|
|
66
|
+
def point_count
|
|
67
|
+
raise NoMethodError, "#{self.class} must override #point_count"
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def point(_index)
|
|
71
|
+
raise NoMethodError, "#{self.class} must override #point(index)"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# ---- Public API ----
|
|
75
|
+
|
|
76
|
+
def fill_color = Color.from_native(C::Graphics.sfShape_getFillColor(@handle))
|
|
77
|
+
|
|
78
|
+
def fill_color=(c)
|
|
79
|
+
C::Graphics.sfShape_setFillColor(@handle, c.to_native)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def outline_color = Color.from_native(C::Graphics.sfShape_getOutlineColor(@handle))
|
|
83
|
+
|
|
84
|
+
def outline_color=(c)
|
|
85
|
+
C::Graphics.sfShape_setOutlineColor(@handle, c.to_native)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def outline_thickness = C::Graphics.sfShape_getOutlineThickness(@handle)
|
|
89
|
+
|
|
90
|
+
def outline_thickness=(t)
|
|
91
|
+
C::Graphics.sfShape_setOutlineThickness(@handle, t.to_f)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Recompute the outline + bounds from the current `#point_count` /
|
|
95
|
+
# `#point(i)`. Call after the data driving your callbacks changes.
|
|
96
|
+
# No-op on construction (CSFML samples the points once at the
|
|
97
|
+
# first draw).
|
|
98
|
+
def update
|
|
99
|
+
C::Graphics.sfShape_update(@handle)
|
|
100
|
+
self
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
104
|
+
target._draw_native(:Shape, @handle, states_ptr)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Abstract Shape doesn't implement #dup — every subclass has its
|
|
108
|
+
# own state and copy semantics. (CSFML has no `sfShape_copy`.)
|
|
109
|
+
undef_method :dup
|
|
110
|
+
undef_method :clone
|
|
111
|
+
|
|
112
|
+
attr_reader :handle # :nodoc:
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Graphics
|
|
3
|
+
# Methods shared across CircleShape / RectangleShape / ConvexShape:
|
|
4
|
+
# texture binding, geometry introspection, transform readout, dup.
|
|
5
|
+
# Including class must define CSFML_PREFIX (Symbol) and hold the FFI
|
|
6
|
+
# handle in @handle (same contract as Graphics::Transformable).
|
|
7
|
+
#
|
|
8
|
+
# Texture binding follows the Sprite pattern: keep a Ruby reference
|
|
9
|
+
# to the bound texture in @texture so the GC doesn't collect the GPU
|
|
10
|
+
# resource while the shape still draws with it.
|
|
11
|
+
module ShapeInspectable
|
|
12
|
+
def texture
|
|
13
|
+
ptr = _csfml(:getTexture, @handle)
|
|
14
|
+
return nil if ptr.null?
|
|
15
|
+
Texture.send(:_borrow, ptr)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# `reset_rect: true` snaps the texture-rect to the full texture size,
|
|
19
|
+
# which is usually what you want when binding a fresh texture.
|
|
20
|
+
def set_texture(tex, reset_rect: false)
|
|
21
|
+
raise ArgumentError, "expected SFML::Texture or nil" unless tex.nil? || tex.is_a?(Texture)
|
|
22
|
+
_csfml(:setTexture, @handle, tex ? tex.handle : nil, reset_rect)
|
|
23
|
+
@texture = tex
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def texture=(tex)
|
|
28
|
+
set_texture(tex, reset_rect: false)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def texture_rect
|
|
32
|
+
Rect.from_native(_csfml(:getTextureRect, @handle))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def texture_rect=(rect)
|
|
36
|
+
raise ArgumentError, "texture_rect= requires a SFML::Rect" unless rect.is_a?(Rect)
|
|
37
|
+
native = C::Graphics::IntRect.new
|
|
38
|
+
native[:position][:x] = Integer(rect.x)
|
|
39
|
+
native[:position][:y] = Integer(rect.y)
|
|
40
|
+
native[:size][:x] = Integer(rect.width)
|
|
41
|
+
native[:size][:y] = Integer(rect.height)
|
|
42
|
+
_csfml(:setTextureRect, @handle, native)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Single polygon vertex by index. The full list is exposed by the
|
|
46
|
+
# subclass-specific `#points` method on ConvexShape; CircleShape
|
|
47
|
+
# synthesises N points from its radius+point_count.
|
|
48
|
+
def point(index)
|
|
49
|
+
Vector2.from_native(_csfml(:getPoint, @handle, Integer(index)))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Centroid of the polygon's vertex list (CSFML 3.0+).
|
|
53
|
+
def geometric_center
|
|
54
|
+
Vector2.from_native(_csfml(:getGeometricCenter, @handle))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# AABB before transform — the rectangle the geometry would occupy
|
|
58
|
+
# if the shape were drawn at the origin with no rotation/scale.
|
|
59
|
+
def local_bounds
|
|
60
|
+
Rect.from_native(_csfml(:getLocalBounds, @handle))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# AABB after the shape's full transform — the world-space rect the
|
|
64
|
+
# renderer uses for culling and hit-tests.
|
|
65
|
+
def global_bounds
|
|
66
|
+
Rect.from_native(_csfml(:getGlobalBounds, @handle))
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def transform
|
|
70
|
+
_csfml(:getTransform, @handle)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def inverse_transform
|
|
74
|
+
_csfml(:getInverseTransform, @handle)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Deep copy. Texture binding is shared (textures are GPU objects,
|
|
78
|
+
# cheap to alias); transform/colour state is independent.
|
|
79
|
+
def dup
|
|
80
|
+
ptr = _csfml(:copy, @handle)
|
|
81
|
+
raise Error, "#{self.class.name}#dup returned NULL" if ptr.null?
|
|
82
|
+
|
|
83
|
+
destroy_fn = C::Graphics.method(:"#{self.class::CSFML_PREFIX}_destroy")
|
|
84
|
+
copy = self.class.allocate
|
|
85
|
+
copy.instance_variable_set(:@handle, FFI::AutoPointer.new(ptr, destroy_fn))
|
|
86
|
+
copy.instance_variable_set(:@texture, @texture) if defined?(@texture)
|
|
87
|
+
copy
|
|
88
|
+
end
|
|
89
|
+
alias clone dup
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -5,8 +5,17 @@ module SFML
|
|
|
5
5
|
# tex = SFML::Texture.load("assets/hero.png")
|
|
6
6
|
# tex = SFML::Texture.load("assets/tile.png", smooth: true, repeated: true)
|
|
7
7
|
class Texture
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
# Load a texture from a file on disk.
|
|
9
|
+
#
|
|
10
|
+
# Pass `srgb: true` if the source pixels are in sRGB-encoded
|
|
11
|
+
# gamma space (most photo/PNG art assets) and you want the GPU
|
|
12
|
+
# to gamma-decode on sample so blending happens in linear space.
|
|
13
|
+
# Pair with `RenderWindow#new(..., srgb_capable: true)` and your
|
|
14
|
+
# final framebuffer will gamma-encode on present.
|
|
15
|
+
def self.load(path, smooth: false, repeated: false, srgb: false)
|
|
16
|
+
ptr = srgb \
|
|
17
|
+
? C::Graphics.sfTexture_createSrgbFromFile(path.to_s, nil) \
|
|
18
|
+
: C::Graphics.sfTexture_createFromFile(path.to_s, nil)
|
|
10
19
|
raise Error, "Could not load texture from #{path.inspect}" if ptr.null?
|
|
11
20
|
|
|
12
21
|
tex = allocate
|
|
@@ -20,10 +29,10 @@ module SFML
|
|
|
20
29
|
# `update(image)` afterwards to upload pixels. Useful when
|
|
21
30
|
# you'll be filling the texture from a procedurally-generated
|
|
22
31
|
# Image or repeatedly streaming pixel data into it.
|
|
23
|
-
def self.create(width, height)
|
|
32
|
+
def self.create(width, height, srgb: false)
|
|
24
33
|
size = C::System::Vector2u.new
|
|
25
34
|
size[:x] = Integer(width); size[:y] = Integer(height)
|
|
26
|
-
ptr = C::Graphics.sfTexture_create(size)
|
|
35
|
+
ptr = srgb ? C::Graphics.sfTexture_createSrgb(size) : C::Graphics.sfTexture_create(size)
|
|
27
36
|
raise Error, "sfTexture_create returned NULL — out of GPU memory?" if ptr.null?
|
|
28
37
|
|
|
29
38
|
tex = allocate
|
|
@@ -34,12 +43,14 @@ module SFML
|
|
|
34
43
|
# Decode + upload a Ruby String of bytes (PNG, JPG, BMP, …) as
|
|
35
44
|
# a texture. Useful for embedded assets / network responses
|
|
36
45
|
# that bypass the disk.
|
|
37
|
-
def self.from_memory(bytes, smooth: false, repeated: false)
|
|
46
|
+
def self.from_memory(bytes, smooth: false, repeated: false, srgb: false)
|
|
38
47
|
raise ArgumentError, "expected a String, got #{bytes.class}" unless bytes.is_a?(String)
|
|
39
48
|
|
|
40
49
|
buf = FFI::MemoryPointer.new(:uint8, bytes.bytesize)
|
|
41
50
|
buf.write_bytes(bytes)
|
|
42
|
-
ptr =
|
|
51
|
+
ptr = srgb \
|
|
52
|
+
? C::Graphics.sfTexture_createSrgbFromMemory(buf, bytes.bytesize, nil) \
|
|
53
|
+
: C::Graphics.sfTexture_createFromMemory(buf, bytes.bytesize, nil)
|
|
43
54
|
raise Error, "sfTexture_createFromMemory returned NULL — unsupported format?" if ptr.null?
|
|
44
55
|
|
|
45
56
|
tex = allocate
|
|
@@ -49,12 +60,32 @@ module SFML
|
|
|
49
60
|
tex
|
|
50
61
|
end
|
|
51
62
|
|
|
63
|
+
# Load a texture from any Ruby IO-like object (File, StringIO,
|
|
64
|
+
# or anything answering read/seek/pos/size). Useful for assets
|
|
65
|
+
# inside a zip archive, served over a socket, or generated on
|
|
66
|
+
# the fly.
|
|
67
|
+
def self.from_stream(io, smooth: false, repeated: false, srgb: false)
|
|
68
|
+
stream = SFML::InputStream.new(io)
|
|
69
|
+
ptr = srgb \
|
|
70
|
+
? C::Graphics.sfTexture_createSrgbFromStream(stream.to_ptr, nil) \
|
|
71
|
+
: C::Graphics.sfTexture_createFromStream(stream.to_ptr, nil)
|
|
72
|
+
raise Error, "sfTexture_createFromStream returned NULL — unsupported format?" if ptr.null?
|
|
73
|
+
|
|
74
|
+
tex = allocate
|
|
75
|
+
tex.send(:_take_ownership, ptr)
|
|
76
|
+
tex.smooth = smooth
|
|
77
|
+
tex.repeated = repeated
|
|
78
|
+
tex
|
|
79
|
+
end
|
|
80
|
+
|
|
52
81
|
# Upload a CPU-side SFML::Image to the GPU as a new Texture. Keeps
|
|
53
82
|
# the RGBA byte order and dimensions of the source image.
|
|
54
|
-
def self.from_image(image, smooth: false, repeated: false)
|
|
83
|
+
def self.from_image(image, smooth: false, repeated: false, srgb: false)
|
|
55
84
|
raise ArgumentError, "Texture.from_image needs a SFML::Image" unless image.is_a?(Image)
|
|
56
85
|
|
|
57
|
-
ptr =
|
|
86
|
+
ptr = srgb \
|
|
87
|
+
? C::Graphics.sfTexture_createSrgbFromImage(image.handle, nil) \
|
|
88
|
+
: C::Graphics.sfTexture_createFromImage(image.handle, nil)
|
|
58
89
|
raise Error, "sfTexture_createFromImage returned NULL" if ptr.null?
|
|
59
90
|
|
|
60
91
|
tex = allocate
|
|
@@ -144,10 +175,15 @@ module SFML
|
|
|
144
175
|
# Reallocate this texture's GPU memory at a new size. Returns
|
|
145
176
|
# `false` if the GPU rejects the size (driver limit / OOM);
|
|
146
177
|
# the texture's contents become undefined on success.
|
|
147
|
-
|
|
178
|
+
# Pass `srgb: true` to re-allocate as an sRGB-encoded texture.
|
|
179
|
+
def resize(width, height, srgb: false)
|
|
148
180
|
size = C::System::Vector2u.new
|
|
149
181
|
size[:x] = Integer(width); size[:y] = Integer(height)
|
|
150
|
-
|
|
182
|
+
if srgb
|
|
183
|
+
C::Graphics.sfTexture_resizeSrgb(@handle, size)
|
|
184
|
+
else
|
|
185
|
+
C::Graphics.sfTexture_resize(@handle, size)
|
|
186
|
+
end
|
|
151
187
|
end
|
|
152
188
|
|
|
153
189
|
# Atomically swap the GPU memory between two textures —
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Standalone transformable — a pure CSFML transform container (no
|
|
3
|
+
# geometry attached). Useful as a base for custom drawables that
|
|
4
|
+
# combine a transform with their own rendering: parent the child's
|
|
5
|
+
# vertices to this object's `#transform` and you get position /
|
|
6
|
+
# rotation / scale / origin for free.
|
|
7
|
+
#
|
|
8
|
+
# Most users want the `Graphics::Transformable` mixin instead — this
|
|
9
|
+
# standalone class exists for symmetry with the C++ API.
|
|
10
|
+
#
|
|
11
|
+
# t = SFML::TransformableObject.new(position: [400, 300], rotation: 45)
|
|
12
|
+
# t.transform #=> SFML::C::Graphics::Transform (use via RenderStates)
|
|
13
|
+
class TransformableObject
|
|
14
|
+
include Graphics::Transformable
|
|
15
|
+
CSFML_PREFIX = :sfTransformable
|
|
16
|
+
|
|
17
|
+
def initialize(**opts)
|
|
18
|
+
ptr = C::Graphics.sfTransformable_create
|
|
19
|
+
raise Error, "sfTransformable_create returned NULL" if ptr.null?
|
|
20
|
+
@handle = FFI::AutoPointer.new(ptr, C::Graphics.method(:sfTransformable_destroy))
|
|
21
|
+
|
|
22
|
+
self.position = opts[:position] if opts.key?(:position)
|
|
23
|
+
self.origin = opts[:origin] if opts.key?(:origin)
|
|
24
|
+
self.rotation = opts[:rotation] if opts.key?(:rotation)
|
|
25
|
+
self.scale = opts[:scale] if opts.key?(:scale)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def transform
|
|
29
|
+
C::Graphics.sfTransformable_getTransform(@handle)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def inverse_transform
|
|
33
|
+
C::Graphics.sfTransformable_getInverseTransform(@handle)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def dup
|
|
37
|
+
ptr = C::Graphics.sfTransformable_copy(@handle)
|
|
38
|
+
raise Error, "sfTransformable_copy returned NULL" if ptr.null?
|
|
39
|
+
copy = self.class.allocate
|
|
40
|
+
copy.instance_variable_set(:@handle,
|
|
41
|
+
FFI::AutoPointer.new(ptr, C::Graphics.method(:sfTransformable_destroy)))
|
|
42
|
+
copy
|
|
43
|
+
end
|
|
44
|
+
alias clone dup
|
|
45
|
+
|
|
46
|
+
attr_reader :handle # :nodoc:
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -109,6 +109,18 @@ module SFML
|
|
|
109
109
|
target._draw_native(:VertexArray, @handle, states_ptr)
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
# Independent copy — vertices duplicated, future appends to one
|
|
113
|
+
# don't affect the other.
|
|
114
|
+
def dup
|
|
115
|
+
ptr = C::Graphics.sfVertexArray_copy(@handle)
|
|
116
|
+
raise Error, "sfVertexArray_copy returned NULL" if ptr.null?
|
|
117
|
+
copy = self.class.allocate
|
|
118
|
+
copy.instance_variable_set(:@handle,
|
|
119
|
+
FFI::AutoPointer.new(ptr, C::Graphics.method(:sfVertexArray_destroy)))
|
|
120
|
+
copy
|
|
121
|
+
end
|
|
122
|
+
alias clone dup
|
|
123
|
+
|
|
112
124
|
attr_reader :handle # :nodoc:
|
|
113
125
|
end
|
|
114
126
|
end
|
|
@@ -108,6 +108,18 @@ module SFML
|
|
|
108
108
|
|
|
109
109
|
def native_handle = C::Graphics.sfVertexBuffer_getNativeHandle(@handle)
|
|
110
110
|
|
|
111
|
+
# Bind this VBO as the active vertex buffer for the GL pipeline.
|
|
112
|
+
# Useful when mixing raw GL with SFML rendering — pair with
|
|
113
|
+
# `SFML::VertexBuffer.unbind` (or any other draw) to release.
|
|
114
|
+
def bind
|
|
115
|
+
C::Graphics.sfVertexBuffer_bind(@handle)
|
|
116
|
+
self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def self.unbind
|
|
120
|
+
C::Graphics.sfVertexBuffer_bind(nil)
|
|
121
|
+
end
|
|
122
|
+
|
|
111
123
|
def draw_on(target, states_ptr = nil) # :nodoc:
|
|
112
124
|
target._draw_native(:VertexBuffer, @handle, states_ptr)
|
|
113
125
|
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
module Network
|
|
3
|
+
# Binary serializer with explicit, typed read/write — wire-compatible
|
|
4
|
+
# with SFML's `sf::Packet`. Used by Tcp/UdpSocket's `send_packet`
|
|
5
|
+
# /`receive_packet` (the network layer prepends a 4-byte length header
|
|
6
|
+
# for TCP framing, so the receiver always gets a whole packet).
|
|
7
|
+
#
|
|
8
|
+
# All read calls consume bytes in FIFO order. After a write you can
|
|
9
|
+
# always re-read by sending the packet over the wire; the local
|
|
10
|
+
# read position only advances on the receive side. To rewind a
|
|
11
|
+
# locally-built packet, just construct a new one.
|
|
12
|
+
#
|
|
13
|
+
# pkt = SFML::Network::Packet.new
|
|
14
|
+
# pkt.write_int32(42)
|
|
15
|
+
# pkt.write_string("hello")
|
|
16
|
+
# pkt.write_float(0.5)
|
|
17
|
+
#
|
|
18
|
+
# # … send over a TcpSocket, receive on the other end …
|
|
19
|
+
#
|
|
20
|
+
# incoming.read_int32 #=> 42
|
|
21
|
+
# incoming.read_string #=> "hello"
|
|
22
|
+
# incoming.read_float #=> 0.5
|
|
23
|
+
class Packet
|
|
24
|
+
def initialize
|
|
25
|
+
ptr = C::Network.sfPacket_create
|
|
26
|
+
raise Error, "sfPacket_create returned NULL" if ptr.null?
|
|
27
|
+
@handle = FFI::AutoPointer.new(ptr, C::Network.method(:sfPacket_destroy))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def clear
|
|
31
|
+
C::Network.sfPacket_clear(@handle)
|
|
32
|
+
self
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Append a raw byte buffer (no length prefix, no type tag) — for
|
|
36
|
+
# interop with hand-crafted binary protocols. Most callers want
|
|
37
|
+
# `write_string` / `write_int32` etc. instead.
|
|
38
|
+
def append(bytes)
|
|
39
|
+
s = bytes.to_s
|
|
40
|
+
buf = FFI::MemoryPointer.new(:uint8, s.bytesize)
|
|
41
|
+
buf.write_bytes(s)
|
|
42
|
+
C::Network.sfPacket_append(@handle, buf, s.bytesize)
|
|
43
|
+
self
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the full packet as a binary String. Includes any length
|
|
47
|
+
# prefixes / type encoding sfPacket added on write.
|
|
48
|
+
def data
|
|
49
|
+
size = C::Network.sfPacket_getDataSize(@handle)
|
|
50
|
+
return "".b if size.zero?
|
|
51
|
+
ptr = C::Network.sfPacket_getData(@handle)
|
|
52
|
+
ptr.read_bytes(size).force_encoding(Encoding::ASCII_8BIT)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def size = C::Network.sfPacket_getDataSize(@handle)
|
|
56
|
+
def read_position = C::Network.sfPacket_getReadPosition(@handle)
|
|
57
|
+
def end_of_packet? = C::Network.sfPacket_endOfPacket(@handle)
|
|
58
|
+
|
|
59
|
+
# `false` if the last read overran the packet — sfPacket "fails"
|
|
60
|
+
# quietly rather than raising, and subsequent reads return zero.
|
|
61
|
+
# Check this after a read sequence to validate the frame.
|
|
62
|
+
def ok? = C::Network.sfPacket_canRead(@handle)
|
|
63
|
+
|
|
64
|
+
# ---- typed writers ----
|
|
65
|
+
def write_bool(v)
|
|
66
|
+
C::Network.sfPacket_writeBool(@handle, v ? true : false); self
|
|
67
|
+
end
|
|
68
|
+
def write_int8(v) = (C::Network.sfPacket_writeInt8(@handle, Integer(v)); self)
|
|
69
|
+
def write_uint8(v) = (C::Network.sfPacket_writeUint8(@handle, Integer(v)); self)
|
|
70
|
+
def write_int16(v) = (C::Network.sfPacket_writeInt16(@handle, Integer(v)); self)
|
|
71
|
+
def write_uint16(v) = (C::Network.sfPacket_writeUint16(@handle, Integer(v)); self)
|
|
72
|
+
def write_int32(v) = (C::Network.sfPacket_writeInt32(@handle, Integer(v)); self)
|
|
73
|
+
def write_uint32(v) = (C::Network.sfPacket_writeUint32(@handle, Integer(v)); self)
|
|
74
|
+
def write_int64(v) = (C::Network.sfPacket_writeInt64(@handle, Integer(v)); self)
|
|
75
|
+
def write_uint64(v) = (C::Network.sfPacket_writeUint64(@handle, Integer(v)); self)
|
|
76
|
+
def write_float(v) = (C::Network.sfPacket_writeFloat(@handle, Float(v)); self)
|
|
77
|
+
def write_double(v) = (C::Network.sfPacket_writeDouble(@handle, Float(v)); self)
|
|
78
|
+
|
|
79
|
+
def write_string(str)
|
|
80
|
+
C::Network.sfPacket_writeString(@handle, str.to_s)
|
|
81
|
+
self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# ---- typed readers ----
|
|
85
|
+
def read_bool = C::Network.sfPacket_readBool(@handle)
|
|
86
|
+
def read_int8 = C::Network.sfPacket_readInt8(@handle)
|
|
87
|
+
def read_uint8 = C::Network.sfPacket_readUint8(@handle)
|
|
88
|
+
def read_int16 = C::Network.sfPacket_readInt16(@handle)
|
|
89
|
+
def read_uint16 = C::Network.sfPacket_readUint16(@handle)
|
|
90
|
+
def read_int32 = C::Network.sfPacket_readInt32(@handle)
|
|
91
|
+
def read_uint32 = C::Network.sfPacket_readUint32(@handle)
|
|
92
|
+
def read_int64 = C::Network.sfPacket_readInt64(@handle)
|
|
93
|
+
def read_uint64 = C::Network.sfPacket_readUint64(@handle)
|
|
94
|
+
def read_float = C::Network.sfPacket_readFloat(@handle)
|
|
95
|
+
def read_double = C::Network.sfPacket_readDouble(@handle)
|
|
96
|
+
|
|
97
|
+
# Read a length-prefixed string. CSFML expects a caller-allocated
|
|
98
|
+
# C buffer of "string length + 1 NUL" bytes — we size it from the
|
|
99
|
+
# remaining packet bytes, which is always an upper bound.
|
|
100
|
+
def read_string
|
|
101
|
+
remaining = size - read_position
|
|
102
|
+
return "" if remaining <= 0
|
|
103
|
+
|
|
104
|
+
buf = FFI::MemoryPointer.new(:char, remaining + 1)
|
|
105
|
+
C::Network.sfPacket_readString(@handle, buf)
|
|
106
|
+
buf.read_string.force_encoding(Encoding::UTF_8)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def dup
|
|
110
|
+
ptr = C::Network.sfPacket_copy(@handle)
|
|
111
|
+
raise Error, "sfPacket_copy returned NULL" if ptr.null?
|
|
112
|
+
|
|
113
|
+
copy = self.class.allocate
|
|
114
|
+
copy.instance_variable_set(:@handle,
|
|
115
|
+
FFI::AutoPointer.new(ptr, C::Network.method(:sfPacket_destroy)))
|
|
116
|
+
copy
|
|
117
|
+
end
|
|
118
|
+
alias clone dup
|
|
119
|
+
|
|
120
|
+
attr_reader :handle # :nodoc:
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -55,6 +55,25 @@ module SFML
|
|
|
55
55
|
[status, buf.read_bytes(n)]
|
|
56
56
|
end
|
|
57
57
|
|
|
58
|
+
# Send a structured SFML::Network::Packet. CSFML frames the wire
|
|
59
|
+
# bytes with a length prefix so the peer's receive_packet always
|
|
60
|
+
# gets a whole packet (no need to handle TCP boundary fragments
|
|
61
|
+
# at the Ruby layer).
|
|
62
|
+
def send_packet(packet)
|
|
63
|
+
raise ArgumentError, "expected SFML::Network::Packet" unless packet.is_a?(Packet)
|
|
64
|
+
code = C::Network.sfTcpSocket_sendPacket(@handle, packet.handle)
|
|
65
|
+
C::Network::STATUSES[code]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Receive into a fresh Packet. Returns [status, packet]; the
|
|
69
|
+
# packet is nil for non-:done statuses.
|
|
70
|
+
def receive_packet
|
|
71
|
+
pkt = Packet.new
|
|
72
|
+
code = C::Network.sfTcpSocket_receivePacket(@handle, pkt.handle)
|
|
73
|
+
status = C::Network::STATUSES[code]
|
|
74
|
+
[status, status == :done ? pkt : nil]
|
|
75
|
+
end
|
|
76
|
+
|
|
58
77
|
def blocking? = C::Network.sfTcpSocket_isBlocking(@handle)
|
|
59
78
|
|
|
60
79
|
def blocking=(value)
|
|
@@ -57,6 +57,29 @@ module SFML
|
|
|
57
57
|
[status, buf.read_bytes(n), sip, sport]
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
# Send a structured SFML::Network::Packet to (to, port).
|
|
61
|
+
def send_packet(packet, to:, port:)
|
|
62
|
+
raise ArgumentError, "expected SFML::Network::Packet" unless packet.is_a?(Packet)
|
|
63
|
+
addr = to.is_a?(IpAddress) ? to : IpAddress.from_string(to)
|
|
64
|
+
code = C::Network.sfUdpSocket_sendPacket(@handle, packet.handle, addr.struct, Integer(port))
|
|
65
|
+
C::Network::STATUSES[code]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Returns [status, packet, sender_ip, sender_port]. Packet is nil
|
|
69
|
+
# for non-:done statuses.
|
|
70
|
+
def receive_packet
|
|
71
|
+
pkt = Packet.new
|
|
72
|
+
sender_addr = C::Network::IpAddress.new
|
|
73
|
+
sender_port = FFI::MemoryPointer.new(:uint16)
|
|
74
|
+
|
|
75
|
+
code = C::Network.sfUdpSocket_receivePacket(
|
|
76
|
+
@handle, pkt.handle, sender_addr.pointer, sender_port,
|
|
77
|
+
)
|
|
78
|
+
status = C::Network::STATUSES[code]
|
|
79
|
+
return [status, nil, nil, nil] unless status == :done
|
|
80
|
+
[status, pkt, IpAddress.wrap(sender_addr), sender_port.read(:uint16)]
|
|
81
|
+
end
|
|
82
|
+
|
|
60
83
|
def blocking? = C::Network.sfUdpSocket_isBlocking(@handle)
|
|
61
84
|
|
|
62
85
|
def blocking=(value)
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
module SFML
|
|
2
|
+
# Bridge between a Ruby IO-like object (anything with `#read(n)`,
|
|
3
|
+
# `#seek(pos)`, `#pos` / `#tell`, and `#size`) and CSFML's
|
|
4
|
+
# `sfInputStream*` parameter on `*_createFromStream` loader
|
|
5
|
+
# functions.
|
|
6
|
+
#
|
|
7
|
+
# Typical use is indirect — pass a Ruby IO to the
|
|
8
|
+
# `.from_stream` factory on Font, Image, Texture, Shader, Music,
|
|
9
|
+
# or SoundBuffer:
|
|
10
|
+
#
|
|
11
|
+
# File.open("assets/hero.png", "rb") do |io|
|
|
12
|
+
# tex = SFML::Texture.from_stream(io)
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# Direct use is for advanced cases (custom virtual filesystems,
|
|
16
|
+
# network streams, etc.). Subclass or build with `.new(io)`:
|
|
17
|
+
#
|
|
18
|
+
# wrapped = SFML::InputStream.new(my_random_access_io)
|
|
19
|
+
# font = SFML::Font.send(:_load_from_stream_handle, wrapped.struct)
|
|
20
|
+
#
|
|
21
|
+
# The InputStream object must outlive the loader call — keep a
|
|
22
|
+
# reference until the CSFML factory returns. The wrapped IO is
|
|
23
|
+
# not closed automatically; close it yourself.
|
|
24
|
+
class InputStream
|
|
25
|
+
# `io` must respond to: read(n), seek(pos, IO::SEEK_SET), pos/tell, size.
|
|
26
|
+
# Ruby's File, StringIO, and Tempfile all qualify.
|
|
27
|
+
def initialize(io)
|
|
28
|
+
@io = io
|
|
29
|
+
|
|
30
|
+
# Strong refs so the GC doesn't free our callbacks before CSFML
|
|
31
|
+
# is done with the struct. CSFML calls them synchronously
|
|
32
|
+
# during the loader function, but may also call them later
|
|
33
|
+
# (Music streams from disk on its audio thread).
|
|
34
|
+
@read_cb = FFI::Function.new(:int64, [:pointer, :size_t, :pointer]) do |buf, size, _user|
|
|
35
|
+
begin
|
|
36
|
+
bytes = @io.read(size) || ""
|
|
37
|
+
buf.write_bytes(bytes) unless bytes.empty?
|
|
38
|
+
bytes.bytesize
|
|
39
|
+
rescue StandardError
|
|
40
|
+
-1
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
@seek_cb = FFI::Function.new(:int64, [:size_t, :pointer]) do |pos, _user|
|
|
45
|
+
begin
|
|
46
|
+
@io.seek(pos, IO::SEEK_SET)
|
|
47
|
+
pos
|
|
48
|
+
rescue StandardError
|
|
49
|
+
-1
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@tell_cb = FFI::Function.new(:int64, [:pointer]) do |_user|
|
|
54
|
+
begin
|
|
55
|
+
@io.respond_to?(:pos) ? @io.pos : @io.tell
|
|
56
|
+
rescue StandardError
|
|
57
|
+
-1
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
@size_cb = FFI::Function.new(:int64, [:pointer]) do |_user|
|
|
62
|
+
begin
|
|
63
|
+
@io.size
|
|
64
|
+
rescue StandardError
|
|
65
|
+
-1
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
@struct = C::System::InputStream.new
|
|
70
|
+
@struct[:read] = @read_cb
|
|
71
|
+
@struct[:seek] = @seek_cb
|
|
72
|
+
@struct[:tell] = @tell_cb
|
|
73
|
+
@struct[:get_size] = @size_cb
|
|
74
|
+
@struct[:user_data] = FFI::Pointer::NULL
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# @!visibility private
|
|
78
|
+
def struct
|
|
79
|
+
@struct
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Pointer suitable for the `sfInputStream*` parameter on
|
|
83
|
+
# `*_createFromStream` loaders.
|
|
84
|
+
def to_ptr
|
|
85
|
+
@struct.pointer
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/sfml/version.rb
CHANGED