polyphony 0.83 → 0.84

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: a1e0c71b9d91e674e4ce004da08cdf6535f56e48b063b63538685eb781654df6
4
- data.tar.gz: 58bf12edd0751ecc552ee01b6eb7042585061f7800f0dfa6fdc0990b87349982
3
+ metadata.gz: ce9742f5b50581c5569df4ad3e2232c7c32392a92e7ab23960373c8a4e3b0de4
4
+ data.tar.gz: a695f3191cc7cefb7d720565c3564b24784beb836528342e8d4fd9e8d0231dda
5
5
  SHA512:
6
- metadata.gz: c35e1c2756556db5bf11012b95c7e729d7ad43f52802c61fa5467d44a6460a2f30f840bc945347e83ed24d35e559b0ca4ac9d5657c9d0c805ffb44774765f867
7
- data.tar.gz: 37c34a7c2cc0b3443c0f33908f40156c223eae233ca08f564a606a7bec5c8c68189b973e6fa40fd1534bb3c9f2f3985b52fcdd33c7ea9069ba21e32e7ebc1760
6
+ metadata.gz: 041cb949e600ef328ed6d1c92badcaa9ef502e4776043d6a7786a811d5935de450f933b8eff4dd6100db6e55ff19878186bd9ef6bb840ac1cdcdb8c53341f51c
7
+ data.tar.gz: fc0542606c3ee5ab044cc5e8175a60fb3fa6a8466422643ff1446fb0271a6db09236b5c0dbc7a632fa47853ae0ac3a2007fa4644a85283b4225f453f6155dbce
data/.gitmodules CHANGED
@@ -0,0 +1,3 @@
1
+ [submodule "vendor/liburing"]
2
+ path = vendor/liburing
3
+ url = https://github.com/axboe/liburing
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.84 2022-03-11
2
+
3
+ - Implement `IO.tee` (#82)
4
+ - Implement `IO#tee_from` (#81)
5
+ - Bundle liburing as git submodule
6
+
1
7
  ## 0.83 2022-03-10
2
8
 
3
9
  - Implement `Polyphony::Pipe` class, `Polyphony.pipe` method (#83)
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- polyphony (0.83)
4
+ polyphony (0.84)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/Rakefile CHANGED
@@ -24,3 +24,17 @@ task :docs do
24
24
  end
25
25
 
26
26
  CLEAN.include "**/*.o", "**/*.so", "**/*.so.*", "**/*.a", "**/*.bundle", "**/*.jar", "pkg", "tmp"
27
+
28
+ task :release do
29
+ require_relative './lib/polyphony/version'
30
+ version = Polyphony::VERSION
31
+
32
+ puts 'Building polyphony...'
33
+ `gem build polyphony.gemspec`
34
+
35
+ puts "Pushing polyphony #{version}..."
36
+ `gem push extralite-#{version}.gem`
37
+
38
+ puts "Cleaning up..."
39
+ `rm *.gem`
40
+ end
@@ -14,7 +14,7 @@
14
14
  #include <errno.h>
15
15
 
16
16
  #include "polyphony.h"
17
- #include "../liburing/liburing.h"
17
+ #include "liburing.h"
18
18
  #include "backend_io_uring_context.h"
19
19
  #include "ruby/thread.h"
20
20
  #include "ruby/io.h"
@@ -304,7 +304,7 @@ int io_uring_backend_defer_submit_and_await(
304
304
  // op was not completed (an exception was raised), so we need to cancel it
305
305
  ctx->result = -ECANCELED;
306
306
  sqe = io_uring_get_sqe(&backend->ring);
307
- io_uring_prep_cancel(sqe, ctx, 0);
307
+ io_uring_prep_cancel(sqe, (__u64)ctx, 0);
308
308
  backend->pending_sqes = 0;
309
309
  io_uring_submit(&backend->ring);
310
310
  }
@@ -971,6 +971,41 @@ VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE chunksize)
971
971
  return io_uring_backend_splice(backend, src, dest, chunksize, 1);
972
972
  }
973
973
 
974
+ VALUE Backend_tee(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
975
+ Backend_t *backend;
976
+ GetBackend(self, backend);
977
+
978
+ int src_fd;
979
+ int dest_fd;
980
+ rb_io_t *src_fptr;
981
+ rb_io_t *dest_fptr;
982
+ VALUE resume_value = Qnil;
983
+
984
+ src_fd = fd_from_io(src, &src_fptr, 0, 0);
985
+ dest_fd = fd_from_io(dest, &dest_fptr, 1, 0);
986
+
987
+ while (1) {
988
+ op_context_t *ctx = context_store_acquire(&backend->store, OP_SPLICE);
989
+ struct io_uring_sqe *sqe = io_uring_get_sqe(&backend->ring);
990
+ int result;
991
+ int completed;
992
+
993
+ io_uring_prep_tee(sqe, src_fd, dest_fd, NUM2INT(maxlen), 0);
994
+
995
+ result = io_uring_backend_defer_submit_and_await(backend, sqe, ctx, &resume_value);
996
+ completed = context_store_release(&backend->store, ctx);
997
+ RAISE_IF_EXCEPTION(resume_value);
998
+ if (!completed) return resume_value;
999
+
1000
+ if (result < 0)
1001
+ rb_syserr_fail(-result, strerror(-result));
1002
+
1003
+ return INT2NUM(result);
1004
+ }
1005
+
1006
+ RB_GC_GUARD(resume_value);
1007
+ }
1008
+
974
1009
  VALUE Backend_connect(VALUE self, VALUE sock, VALUE host, VALUE port) {
975
1010
  Backend_t *backend;
976
1011
  int fd;
@@ -1125,7 +1160,7 @@ VALUE Backend_timeout_ensure(VALUE arg) {
1125
1160
  timeout_ctx->ctx->result = -ECANCELED;
1126
1161
  // op was not completed, so we need to cancel it
1127
1162
  sqe = io_uring_get_sqe(&timeout_ctx->backend->ring);
1128
- io_uring_prep_cancel(sqe, timeout_ctx->ctx, 0);
1163
+ io_uring_prep_cancel(sqe, (__u64)timeout_ctx->ctx, 0);
1129
1164
  timeout_ctx->backend->pending_sqes = 0;
1130
1165
  io_uring_submit(&timeout_ctx->backend->ring);
1131
1166
  }
@@ -1318,7 +1353,7 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1318
1353
  ctx->ref_count = sqe_count;
1319
1354
  ctx->result = -ECANCELED;
1320
1355
  sqe = io_uring_get_sqe(&backend->ring);
1321
- io_uring_prep_cancel(sqe, ctx, 0);
1356
+ io_uring_prep_cancel(sqe, (__u64)ctx, 0);
1322
1357
  backend->pending_sqes = 0;
1323
1358
  io_uring_submit(&backend->ring);
1324
1359
  }
@@ -1349,7 +1384,7 @@ VALUE Backend_chain(int argc,VALUE *argv, VALUE self) {
1349
1384
  // op was not completed (an exception was raised), so we need to cancel it
1350
1385
  ctx->result = -ECANCELED;
1351
1386
  sqe = io_uring_get_sqe(&backend->ring);
1352
- io_uring_prep_cancel(sqe, ctx, 0);
1387
+ io_uring_prep_cancel(sqe, (__u64)ctx, 0);
1353
1388
  backend->pending_sqes = 0;
1354
1389
  io_uring_submit(&backend->ring);
1355
1390
  RAISE_IF_EXCEPTION(resume_value);
@@ -1416,7 +1451,7 @@ static inline void splice_chunks_cancel(Backend_t *backend, op_context_t *ctx) {
1416
1451
 
1417
1452
  ctx->result = -ECANCELED;
1418
1453
  sqe = io_uring_get_sqe(&backend->ring);
1419
- io_uring_prep_cancel(sqe, ctx, 0);
1454
+ io_uring_prep_cancel(sqe, (__u64)ctx, 0);
1420
1455
  backend->pending_sqes = 0;
1421
1456
  io_uring_submit(&backend->ring);
1422
1457
  }
@@ -1619,6 +1654,7 @@ void Init_Backend() {
1619
1654
  rb_define_method(cBackend, "sleep", Backend_sleep, 1);
1620
1655
  rb_define_method(cBackend, "splice", Backend_splice, 3);
1621
1656
  rb_define_method(cBackend, "splice_to_eof", Backend_splice_to_eof, 3);
1657
+ rb_define_method(cBackend, "tee", Backend_tee, 3);
1622
1658
  rb_define_method(cBackend, "timeout", Backend_timeout, -1);
1623
1659
  rb_define_method(cBackend, "timer_loop", Backend_timer_loop, 1);
1624
1660
  rb_define_method(cBackend, "wait_event", Backend_wait_event, 1);
@@ -940,7 +940,52 @@ VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
940
940
  error:
941
941
  return RAISE_EXCEPTION(switchpoint_result);
942
942
  }
943
+
944
+ VALUE Backend_tee(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
945
+ Backend_t *backend;
946
+ struct libev_rw_io watcher;
947
+ VALUE switchpoint_result = Qnil;
948
+ int src_fd;
949
+ int dest_fd;
950
+ rb_io_t *src_fptr;
951
+ rb_io_t *dest_fptr;
952
+ int len;
953
+
954
+ GetBackend(self, backend);
955
+ src_fd = fd_from_io(src, &src_fptr, 0, 0);
956
+ dest_fd = fd_from_io(dest, &dest_fptr, 1, 0);
957
+ watcher.ctx.fiber = Qnil;
958
+
959
+ while (1) {
960
+ backend->base.op_count++;
961
+ len = tee(src_fd, dest_fd, NUM2INT(maxlen), 0);
962
+ if (len < 0) {
963
+ int e = errno;
964
+ if ((e != EWOULDBLOCK && e != EAGAIN)) rb_syserr_fail(e, strerror(e));
965
+
966
+ switchpoint_result = libev_wait_rw_fd_with_watcher(backend, src_fd, dest_fd, &watcher);
967
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
968
+ }
969
+ else {
970
+ break;
971
+ }
972
+ }
973
+
974
+ if (watcher.ctx.fiber == Qnil) {
975
+ switchpoint_result = backend_snooze(&backend->base);
976
+ if (TEST_EXCEPTION(switchpoint_result)) goto error;
977
+ }
978
+
979
+ RB_GC_GUARD(watcher.ctx.fiber);
980
+ RB_GC_GUARD(switchpoint_result);
981
+
982
+ return INT2NUM(len);
983
+ error:
984
+ return RAISE_EXCEPTION(switchpoint_result);
985
+ }
986
+
943
987
  #else
988
+
944
989
  VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen) {
945
990
  Backend_t *backend;
946
991
  struct libev_io watcher;
@@ -28,6 +28,22 @@ puts "Building Polyphony... (#{config.inspect})"
28
28
 
29
29
  require_relative 'zlib_conf'
30
30
 
31
+ if config[:io_uring]
32
+ liburing_path = File.expand_path('../../vendor/liburing', __dir__)
33
+ FileUtils.cd liburing_path do
34
+ system('./configure', exception: true)
35
+ FileUtils.cd File.join(liburing_path, 'src') do
36
+ system('make', 'liburing.a', exception: true)
37
+ end
38
+ end
39
+
40
+ if !find_header 'liburing.h', File.expand_path('../../vendor/liburing/src/include', __dir__)
41
+ raise "Couldn't find liburing.h"
42
+ end
43
+
44
+ $LDFLAGS << " -L#{File.expand_path('../../vendor/liburing/src', __dir__)} -l uring"
45
+ end
46
+
31
47
  $defs << '-DPOLYPHONY_USE_PIDFD_OPEN' if config[:pidfd_open]
32
48
  if config[:io_uring]
33
49
  $defs << "-DPOLYPHONY_BACKEND_LIBURING"
@@ -56,4 +72,5 @@ CONFIG['optflags'] << ' -fno-strict-aliasing' unless RUBY_PLATFORM =~ /mswin/
56
72
 
57
73
  have_func('rb_fiber_transfer', 'ruby.h')
58
74
 
75
+
59
76
  create_makefile 'polyphony_ext'
@@ -94,6 +94,10 @@ VALUE Polyphony_backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE c
94
94
  return Backend_splice_to_eof(BACKEND(), src, dest, chunksize);
95
95
  }
96
96
 
97
+ VALUE Polyphony_backend_tee(VALUE self, VALUE src, VALUE dest, VALUE chunksize) {
98
+ return Backend_tee(BACKEND(), src, dest, chunksize);
99
+ }
100
+
97
101
  VALUE Polyphony_backend_timeout(int argc,VALUE *argv, VALUE self) {
98
102
  return Backend_timeout(argc, argv, BACKEND());
99
103
  }
@@ -186,6 +190,7 @@ void Init_Polyphony() {
186
190
  rb_define_singleton_method(mPolyphony, "backend_sleep", Polyphony_backend_sleep, 1);
187
191
  rb_define_singleton_method(mPolyphony, "backend_splice", Polyphony_backend_splice, 3);
188
192
  rb_define_singleton_method(mPolyphony, "backend_splice_to_eof", Polyphony_backend_splice_to_eof, 3);
193
+ rb_define_singleton_method(mPolyphony, "backend_tee", Polyphony_backend_tee, 3);
189
194
  rb_define_singleton_method(mPolyphony, "backend_timeout", Polyphony_backend_timeout, -1);
190
195
  rb_define_singleton_method(mPolyphony, "backend_timer_loop", Polyphony_backend_timer_loop, 1);
191
196
  rb_define_singleton_method(mPolyphony, "backend_wait_event", Polyphony_backend_wait_event, 1);
@@ -112,6 +112,7 @@ VALUE Backend_sendv(VALUE self, VALUE io, VALUE ary, VALUE flags);
112
112
  VALUE Backend_sleep(VALUE self, VALUE duration);
113
113
  VALUE Backend_splice(VALUE self, VALUE src, VALUE dest, VALUE maxlen);
114
114
  VALUE Backend_splice_to_eof(VALUE self, VALUE src, VALUE dest, VALUE chunksize);
115
+ VALUE Backend_tee(VALUE self, VALUE src, VALUE dest, VALUE maxlen);
115
116
  VALUE Backend_timeout(int argc,VALUE *argv, VALUE self);
116
117
  VALUE Backend_timer_loop(VALUE self, VALUE interval);
117
118
  VALUE Backend_wait_event(VALUE self, VALUE raise);
@@ -81,6 +81,12 @@ class ::IO
81
81
  def splice_to_eof(src, dest, chunk_size = 8192)
82
82
  Polyphony.backend_splice_to_eof(src, dest, chunk_size)
83
83
  end
84
+
85
+ if RUBY_PLATFORM =~ /linux/
86
+ def tee(src, dest, maxlen)
87
+ Polyphony.backend_tee(src, dest, maxlen)
88
+ end
89
+ end
84
90
  end
85
91
  end
86
92
 
@@ -272,4 +278,8 @@ class ::IO
272
278
  def splice_to_eof_from(src, chunksize = 8192)
273
279
  Polyphony.backend_splice_to_eof(src, self, chunksize)
274
280
  end
281
+
282
+ def tee_from(src, maxlen)
283
+ Polyphony.backend_tee(src, self, maxlen)
284
+ end
275
285
  end
@@ -164,4 +164,8 @@ class Polyphony::Pipe
164
164
  def splice_to_eof_from(src, chunksize = 8192)
165
165
  Polyphony.backend_splice_to_eof(src, self, chunksize)
166
166
  end
167
+
168
+ def tee_from(src, maxlen)
169
+ Polyphony.backend_tee(src, self, maxlen)
170
+ end
167
171
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Polyphony
4
- VERSION = '0.83'
4
+ VERSION = '0.84'
5
5
  end
data/test/test_io.rb CHANGED
@@ -367,6 +367,65 @@ class IOTest < MiniTest::Test
367
367
  f.await
368
368
  end
369
369
  end
370
+
371
+ def test_tee_from
372
+ skip "tested only on Linux" unless RUBY_PLATFORM =~ /linux/
373
+
374
+ src = Polyphony.pipe
375
+ dest1 = Polyphony.pipe
376
+ dest2 = Polyphony.pipe
377
+
378
+ len1 = len2 = nil
379
+
380
+ spin {
381
+ len1 = dest1.tee_from(src, 1000)
382
+ dest1.close
383
+ len2 = IO.splice(src, dest2, 1000)
384
+ dest2.close
385
+ }
386
+
387
+ src << 'foobar'
388
+ src.close
389
+ result1 = dest1.read
390
+ result2 = dest2.read
391
+
392
+ assert_equal 'foobar', result1
393
+ assert_equal 6, len1
394
+
395
+ assert_equal 'foobar', result2
396
+ assert_equal 6, len2
397
+ end
398
+
399
+ def test_tee_class_method
400
+ skip "tested only on Linux" unless RUBY_PLATFORM =~ /linux/
401
+
402
+ src = Polyphony.pipe
403
+ dest1 = Polyphony.pipe
404
+ dest2 = Polyphony.pipe
405
+
406
+ len1 = len2 = nil
407
+
408
+ spin {
409
+ len1 = IO.tee(src, dest1, 1000)
410
+ dest1.close
411
+ len2 = IO.splice(src, dest2, 1000)
412
+ dest2.close
413
+ }
414
+
415
+ src << 'foobar'
416
+ src.close
417
+ result1 = dest1.read
418
+ result2 = dest2.read
419
+
420
+ assert_equal 'foobar', result1
421
+ assert_equal 6, len1
422
+
423
+ assert_equal 'foobar', result2
424
+ assert_equal 6, len2
425
+ end
426
+
427
+
428
+
370
429
  end
371
430
 
372
431
  class IOWithRawBufferTest < MiniTest::Test
@@ -643,7 +702,7 @@ class IOExtensionsTest < MiniTest::Test
643
702
  gz = Zlib::GzipReader.new(dest)
644
703
  data = gz.read
645
704
  assert_equal IO.read(__FILE__), data
646
- assert_in_range (now-1)..(now+1), gz.mtime
705
+ assert_in_range (now-2)..(now+1), gz.mtime
647
706
  assert_nil gz.orig_name
648
707
  assert_nil gz.comment
649
708
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: polyphony
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.83'
4
+ version: '0.84'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-10 00:00:00.000000000 Z
11
+ date: 2022-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -315,16 +315,6 @@ files:
315
315
  - ext/libev/ev_win32.c
316
316
  - ext/libev/ev_wrap.h
317
317
  - ext/libev/test_libev_win32.c
318
- - ext/liburing/liburing.h
319
- - ext/liburing/liburing/README.md
320
- - ext/liburing/liburing/barrier.h
321
- - ext/liburing/liburing/compat.h
322
- - ext/liburing/liburing/io_uring.h
323
- - ext/liburing/queue.c
324
- - ext/liburing/register.c
325
- - ext/liburing/setup.c
326
- - ext/liburing/syscall.c
327
- - ext/liburing/syscall.h
328
318
  - ext/polyphony/backend_common.c
329
319
  - ext/polyphony/backend_common.h
330
320
  - ext/polyphony/backend_io_uring.c
@@ -337,7 +327,6 @@ files:
337
327
  - ext/polyphony/io_extensions.c
338
328
  - ext/polyphony/libev.c
339
329
  - ext/polyphony/libev.h
340
- - ext/polyphony/liburing.c
341
330
  - ext/polyphony/pipe.c
342
331
  - ext/polyphony/playground.c
343
332
  - ext/polyphony/polyphony.c
@@ -1,4 +0,0 @@
1
- ## Updating the liburing source code
2
-
3
- - copy liburing/src/**/* into ext/liburing
4
- - move ext/liburing/include/**/* to ext/liburing/
@@ -1,73 +0,0 @@
1
- /* SPDX-License-Identifier: MIT */
2
- #ifndef LIBURING_BARRIER_H
3
- #define LIBURING_BARRIER_H
4
-
5
- /*
6
- From the kernel documentation file refcount-vs-atomic.rst:
7
-
8
- A RELEASE memory ordering guarantees that all prior loads and
9
- stores (all po-earlier instructions) on the same CPU are completed
10
- before the operation. It also guarantees that all po-earlier
11
- stores on the same CPU and all propagated stores from other CPUs
12
- must propagate to all other CPUs before the release operation
13
- (A-cumulative property). This is implemented using
14
- :c:func:`smp_store_release`.
15
-
16
- An ACQUIRE memory ordering guarantees that all post loads and
17
- stores (all po-later instructions) on the same CPU are
18
- completed after the acquire operation. It also guarantees that all
19
- po-later stores on the same CPU must propagate to all other CPUs
20
- after the acquire operation executes. This is implemented using
21
- :c:func:`smp_acquire__after_ctrl_dep`.
22
- */
23
-
24
- #ifdef __cplusplus
25
- #include <atomic>
26
-
27
- template <typename T>
28
- static inline void IO_URING_WRITE_ONCE(T &var, T val)
29
- {
30
- std::atomic_store_explicit(reinterpret_cast<std::atomic<T> *>(&var),
31
- val, std::memory_order_relaxed);
32
- }
33
- template <typename T>
34
- static inline T IO_URING_READ_ONCE(const T &var)
35
- {
36
- return std::atomic_load_explicit(
37
- reinterpret_cast<const std::atomic<T> *>(&var),
38
- std::memory_order_relaxed);
39
- }
40
-
41
- template <typename T>
42
- static inline void io_uring_smp_store_release(T *p, T v)
43
- {
44
- std::atomic_store_explicit(reinterpret_cast<std::atomic<T> *>(p), v,
45
- std::memory_order_release);
46
- }
47
-
48
- template <typename T>
49
- static inline T io_uring_smp_load_acquire(const T *p)
50
- {
51
- return std::atomic_load_explicit(
52
- reinterpret_cast<const std::atomic<T> *>(p),
53
- std::memory_order_acquire);
54
- }
55
- #else
56
- #include <stdatomic.h>
57
-
58
- #define IO_URING_WRITE_ONCE(var, val) \
59
- atomic_store_explicit((_Atomic typeof(var) *)&(var), \
60
- (val), memory_order_relaxed)
61
- #define IO_URING_READ_ONCE(var) \
62
- atomic_load_explicit((_Atomic typeof(var) *)&(var), \
63
- memory_order_relaxed)
64
-
65
- #define io_uring_smp_store_release(p, v) \
66
- atomic_store_explicit((_Atomic typeof(*(p)) *)(p), (v), \
67
- memory_order_release)
68
- #define io_uring_smp_load_acquire(p) \
69
- atomic_load_explicit((_Atomic typeof(*(p)) *)(p), \
70
- memory_order_acquire)
71
- #endif
72
-
73
- #endif /* defined(LIBURING_BARRIER_H) */
@@ -1,15 +0,0 @@
1
- /* SPDX-License-Identifier: MIT */
2
- #ifndef LIBURING_COMPAT_H
3
- #define LIBURING_COMPAT_H
4
-
5
- #include <linux/time_types.h>
6
-
7
- #include <inttypes.h>
8
-
9
- struct open_how {
10
- uint64_t flags;
11
- uint64_t mode;
12
- uint64_t resolve;
13
- };
14
-
15
- #endif