@geastack/core 0.1.33 → 0.1.35
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.
- package/app_frame_perf.cpp +12 -0
- package/audio-worklet.d.ts +49 -0
- package/gea_app_entry.cpp +36 -3
- package/gea_sources.mjs +2 -2
- package/include/app.h +6 -0
- package/include/audio.h +11 -0
- package/include/audio_stream.h +73 -0
- package/include/canvas.h +1 -0
- package/include/pixel.h +40 -6
- package/index.d.ts +228 -8
- package/package.json +5 -2
- package/runtime/audio-worklet.ts +51 -0
- package/runtime-surface.ts +7 -0
- package/scripts/build-gea-vite-geatsc.mjs +51 -3
- package/scripts/gea-bundle-type-hints.mjs +9 -1
- package/scripts/gea-embedded-compat-staging.mjs +2 -0
- package/scripts/gea-static-asset.mjs +15 -0
- package/scripts/gea-vite-module-graph-plugin.mjs +1 -14
- package/scripts/gea-worker-modules.mjs +192 -0
- package/scripts/generate-gea-embedded-assets.mjs +2 -1
- package/typed-arrays.d.ts +13 -0
- package/workers.d.ts +66 -0
package/app_frame_perf.cpp
CHANGED
|
@@ -30,6 +30,12 @@ const char *applicationFramePhaseName(ApplicationFramePhase phase)
|
|
|
30
30
|
case ApplicationFramePhase::StyleRecompute: return "style_recompute";
|
|
31
31
|
case ApplicationFramePhase::DocumentFrame: return "document_frame";
|
|
32
32
|
case ApplicationFramePhase::RefreshMounted: return "refresh_mounted";
|
|
33
|
+
case ApplicationFramePhase::CycleCollection: return "cycle_collection";
|
|
34
|
+
case ApplicationFramePhase::CallbackMicrotasks: return "callback_microtasks";
|
|
35
|
+
case ApplicationFramePhase::WebSocketCallbacks: return "websocket_callbacks";
|
|
36
|
+
case ApplicationFramePhase::RtcCallbacks: return "rtc_callbacks";
|
|
37
|
+
case ApplicationFramePhase::VideoPresentation: return "video_presentation";
|
|
38
|
+
case ApplicationFramePhase::HttpRequests: return "http_requests";
|
|
33
39
|
case ApplicationFramePhase::Idle: return "idle";
|
|
34
40
|
}
|
|
35
41
|
return "idle";
|
|
@@ -59,6 +65,12 @@ void applicationFramePerfStatsAdd(ApplicationFramePerfStats &stats, ApplicationF
|
|
|
59
65
|
case ApplicationFramePhase::RefreshMounted:
|
|
60
66
|
stats.refreshMountedUs += durationUs;
|
|
61
67
|
break;
|
|
68
|
+
case ApplicationFramePhase::CycleCollection:
|
|
69
|
+
case ApplicationFramePhase::CallbackMicrotasks:
|
|
70
|
+
case ApplicationFramePhase::WebSocketCallbacks:
|
|
71
|
+
case ApplicationFramePhase::RtcCallbacks:
|
|
72
|
+
case ApplicationFramePhase::VideoPresentation:
|
|
73
|
+
case ApplicationFramePhase::HttpRequests:
|
|
62
74
|
case ApplicationFramePhase::Idle:
|
|
63
75
|
break;
|
|
64
76
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AudioContext, AudioDestinationNode } from './index'
|
|
2
|
+
import type { MessagePort } from './workers'
|
|
3
|
+
|
|
4
|
+
export interface AudioContextOptions {
|
|
5
|
+
sampleRate?: number
|
|
6
|
+
latencyHint?: 'interactive' | 'balanced' | 'playback' | number
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AudioWorklet {
|
|
10
|
+
addModule(moduleURL: string | URL): Promise<void>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface AudioWorkletNodeOptions {
|
|
14
|
+
numberOfInputs?: number
|
|
15
|
+
numberOfOutputs?: number
|
|
16
|
+
channelCount?: number
|
|
17
|
+
outputChannelCount?: number[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface AudioWorkletNode {
|
|
21
|
+
readonly port: MessagePort
|
|
22
|
+
onprocessorerror: ((event: { readonly message: string }) => void) | null
|
|
23
|
+
connect(destination: AudioDestinationNode): AudioDestinationNode
|
|
24
|
+
disconnect(): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface AudioWorkletNodeConstructor {
|
|
28
|
+
new (context: AudioContext, name: string, options?: AudioWorkletNodeOptions): AudioWorkletNode
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface MediaStreamAudioSourceNode {
|
|
32
|
+
connect(destination: AudioWorkletNode): AudioWorkletNode
|
|
33
|
+
disconnect(): void
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare global {
|
|
37
|
+
const AudioWorkletNode: AudioWorkletNodeConstructor
|
|
38
|
+
abstract class AudioWorkletProcessor {
|
|
39
|
+
readonly port: MessagePort
|
|
40
|
+
constructor()
|
|
41
|
+
abstract process(inputs: Float32Array[][], outputs: Float32Array[][],
|
|
42
|
+
parameters: Record<string, Float32Array>): boolean
|
|
43
|
+
}
|
|
44
|
+
function registerProcessor(name: string, processorCtor: new () => AudioWorkletProcessor): void
|
|
45
|
+
/** AudioWorkletGlobalScope clock, in the context's sample-rate domain. */
|
|
46
|
+
const sampleRate: number
|
|
47
|
+
const currentFrame: number
|
|
48
|
+
const currentTime: number
|
|
49
|
+
}
|
package/gea_app_entry.cpp
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
#include "gea_perf_config.h" // GEA_EMBEDDED_PERF master + per-subsystem perf flags
|
|
7
7
|
#include "gea/embedded.h"
|
|
8
8
|
#include "host/backends.h"
|
|
9
|
+
#include "host/worker.h"
|
|
9
10
|
#include "ui/style.h"
|
|
10
11
|
#include "ui/tree_internal.h" // Tree::mount for the post-mount clean repaint in init()
|
|
11
12
|
|
|
@@ -64,7 +65,23 @@ public:
|
|
|
64
65
|
{
|
|
65
66
|
if (active_) gea_cycle_collection_defer_begin();
|
|
66
67
|
}
|
|
67
|
-
~FrameCycleCollectionDeferral() {
|
|
68
|
+
~FrameCycleCollectionDeferral() {
|
|
69
|
+
#if defined(ESP_PLATFORM) && CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
|
70
|
+
const auto started = nowUs();
|
|
71
|
+
#endif
|
|
72
|
+
if (active_) {
|
|
73
|
+
// Scope-exit collection runs after frame phases finish. Keep it
|
|
74
|
+
// visible to the watchdog instead of reporting a stalled frame as idle.
|
|
75
|
+
const auto previous = applicationFramePhaseRead();
|
|
76
|
+
applicationFramePhaseSet(ApplicationFramePhase::CycleCollection);
|
|
77
|
+
gea_cycle_collection_defer_end();
|
|
78
|
+
applicationFramePhaseSet(previous);
|
|
79
|
+
}
|
|
80
|
+
#if defined(ESP_PLATFORM) && CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
|
81
|
+
const auto elapsed = nowUs() - started;
|
|
82
|
+
if (elapsed > 30000) ESP_LOGW("frame_phase", "cycle_collection duration_ms=%lld", (long long)(elapsed / 1000));
|
|
83
|
+
#endif
|
|
84
|
+
}
|
|
68
85
|
|
|
69
86
|
private:
|
|
70
87
|
bool active_;
|
|
@@ -209,7 +226,7 @@ void logFramePerfWindow(int64_t windowEndUs)
|
|
|
209
226
|
// pays zero for phase instrumentation.
|
|
210
227
|
std::int64_t phaseClockUs()
|
|
211
228
|
{
|
|
212
|
-
#if GEA_FRAME_PHASE_TIMING
|
|
229
|
+
#if GEA_FRAME_PHASE_TIMING || CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
|
213
230
|
return nowUs();
|
|
214
231
|
#else
|
|
215
232
|
return 0;
|
|
@@ -222,6 +239,11 @@ std::int64_t phaseClockUs()
|
|
|
222
239
|
// neither is set.
|
|
223
240
|
void recordFramePhase(ApplicationFramePhase phase, int64_t durationUs)
|
|
224
241
|
{
|
|
242
|
+
#if defined(ESP_PLATFORM) && CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS
|
|
243
|
+
if (durationUs > 30000)
|
|
244
|
+
ESP_LOGW("frame_phase", "%s duration_ms=%lld", applicationFramePhaseName(phase),
|
|
245
|
+
static_cast<long long>(durationUs / 1000));
|
|
246
|
+
#endif
|
|
225
247
|
#if GEA_EMBEDDED_FRAME_SCHEDULER_PERF_LOG
|
|
226
248
|
applicationFramePerfStatsAdd(phase, durationUs);
|
|
227
249
|
#endif
|
|
@@ -273,11 +295,22 @@ void Application::frame(int timestampMs)
|
|
|
273
295
|
startUs = phaseClockUs();
|
|
274
296
|
applicationFramePhaseSet(ApplicationFramePhase::AnimationFrameCallbacks);
|
|
275
297
|
gea::host::runAnimationFrameCallbacks(static_cast<double>(timestampMs));
|
|
298
|
+
applicationFramePhaseSet(ApplicationFramePhase::CallbackMicrotasks);
|
|
276
299
|
generated::drainMicrotasks();
|
|
277
|
-
//
|
|
300
|
+
// The build already proves whether network services are reachable. Keep their
|
|
301
|
+
// callback pumps out of offline frame loops, while still servicing local workers.
|
|
278
302
|
#if !defined(GEA_EMBEDDED_NETWORK_SERVICES_DISABLED) || !GEA_EMBEDDED_NETWORK_SERVICES_DISABLED
|
|
303
|
+
applicationFramePhaseSet(ApplicationFramePhase::WebSocketCallbacks);
|
|
279
304
|
gea::host::websocket::runCallbacks();
|
|
305
|
+
applicationFramePhaseSet(ApplicationFramePhase::RtcCallbacks);
|
|
280
306
|
gea::host::rtc::runCallbacks();
|
|
307
|
+
#else
|
|
308
|
+
gea::host::workers::Context::runMainPending();
|
|
309
|
+
#endif
|
|
310
|
+
applicationFramePhaseSet(ApplicationFramePhase::VideoPresentation);
|
|
311
|
+
gea::host::video::presentFrames();
|
|
312
|
+
#if !defined(GEA_EMBEDDED_NETWORK_SERVICES_DISABLED) || !GEA_EMBEDDED_NETWORK_SERVICES_DISABLED
|
|
313
|
+
applicationFramePhaseSet(ApplicationFramePhase::HttpRequests);
|
|
281
314
|
gea::host::http::runRequests();
|
|
282
315
|
#endif
|
|
283
316
|
recordFramePhase(ApplicationFramePhase::AnimationFrameCallbacks, phaseClockUs() - startUs);
|
package/gea_sources.mjs
CHANGED
|
@@ -87,8 +87,8 @@ export function cxxSources(env = process.env) {
|
|
|
87
87
|
// @geastack/host — platform layer + services
|
|
88
88
|
...cpp(host, ['input', 'wifi', 'bluetooth', 'geolocation']),
|
|
89
89
|
...cpp(join(host, 'host'), [
|
|
90
|
-
'timers', 'input', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
|
|
91
|
-
'ble', 'apps', 'audio', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
|
|
90
|
+
'timers', 'worker', 'input', 'display', 'fetch', 'media', 'rtc', 'websocket', 'http', 'wifi',
|
|
91
|
+
'ble', 'apps', 'audio', 'audio_worklet', 'video', 'mjpeg', 'image', 'touch', 'imu', 'memory', 'camera', 'geolocation',
|
|
92
92
|
'tile_loader'
|
|
93
93
|
]),
|
|
94
94
|
// framework services (built on the platform abstraction — no target-specific deps)
|
package/include/app.h
CHANGED
|
@@ -31,6 +31,12 @@ enum class ApplicationFramePhase : std::uint8_t {
|
|
|
31
31
|
StyleRecompute,
|
|
32
32
|
DocumentFrame,
|
|
33
33
|
RefreshMounted,
|
|
34
|
+
CycleCollection,
|
|
35
|
+
CallbackMicrotasks,
|
|
36
|
+
WebSocketCallbacks,
|
|
37
|
+
RtcCallbacks,
|
|
38
|
+
VideoPresentation,
|
|
39
|
+
HttpRequests,
|
|
34
40
|
};
|
|
35
41
|
|
|
36
42
|
struct ApplicationFramePerfStats {
|
package/include/audio.h
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#include <cstddef>
|
|
6
6
|
#include <cstdint>
|
|
7
7
|
#include <string>
|
|
8
|
+
#include <functional>
|
|
8
9
|
|
|
9
10
|
namespace gea::platform::audio {
|
|
10
11
|
|
|
@@ -72,6 +73,14 @@ public:
|
|
|
72
73
|
static bool playFile(const std::string &path);
|
|
73
74
|
static bool playPcm(const std::int16_t *samples, std::size_t sample_count, int sample_rate, int channels);
|
|
74
75
|
static void stopPlayback();
|
|
76
|
+
static void flushPlayback();
|
|
77
|
+
// Continuous 16 kHz mono source; zero means the platform could not attach it.
|
|
78
|
+
static std::uint64_t playPcmStream(std::function<std::size_t(std::int16_t*, std::size_t)> pull);
|
|
79
|
+
static void stopPcmStream(std::uint64_t stream);
|
|
80
|
+
static bool pcmStreamSettled(std::uint64_t stream);
|
|
81
|
+
// Maximum mixer and device buffering delays, in seconds.
|
|
82
|
+
static double processingLatency();
|
|
83
|
+
static double outputLatency();
|
|
75
84
|
};
|
|
76
85
|
|
|
77
86
|
#ifdef GEA_AUDIO_DRIVER_INTERNAL
|
|
@@ -80,8 +89,10 @@ public:
|
|
|
80
89
|
static bool open(int sample_rate, int channels, int bits_per_sample);
|
|
81
90
|
static bool write(const std::int16_t *pcm, std::size_t sample_count, int timeout_ms);
|
|
82
91
|
static void close();
|
|
92
|
+
static bool flush();
|
|
83
93
|
static int volume();
|
|
84
94
|
static void setVolume(int volume_percent);
|
|
95
|
+
static double outputLatency(int sample_rate);
|
|
85
96
|
};
|
|
86
97
|
#endif
|
|
87
98
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
#pragma once
|
|
3
|
+
|
|
4
|
+
#include <algorithm>
|
|
5
|
+
#include <array>
|
|
6
|
+
#include <cstdint>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <map>
|
|
9
|
+
#include <mutex>
|
|
10
|
+
|
|
11
|
+
namespace gea::platform::audio {
|
|
12
|
+
|
|
13
|
+
// The RTC decoder supplies 16 kHz mono PCM. Pulling from independent readers
|
|
14
|
+
// avoids copying every incoming packet into another playback queue.
|
|
15
|
+
class PcmStreamMixer {
|
|
16
|
+
public:
|
|
17
|
+
using Pull = std::function<std::size_t(std::int16_t*, std::size_t)>;
|
|
18
|
+
using Handle = std::uint64_t;
|
|
19
|
+
static constexpr std::size_t chunkFrames = 512;
|
|
20
|
+
|
|
21
|
+
Handle add(Pull pull) {
|
|
22
|
+
if (!pull) return 0;
|
|
23
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
24
|
+
const auto id = ++next_;
|
|
25
|
+
sources_.emplace(id, Source{std::move(pull)});
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
void remove(Handle id) { std::lock_guard<std::mutex> lock(mutex_); sources_.erase(id); }
|
|
29
|
+
void clear() { std::lock_guard<std::mutex> lock(mutex_); sources_.clear(); }
|
|
30
|
+
bool active() const { std::lock_guard<std::mutex> lock(mutex_); return !sources_.empty(); }
|
|
31
|
+
bool settled(Handle id) const {
|
|
32
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
33
|
+
const auto it = sources_.find(id);
|
|
34
|
+
return it == sources_.end() || (!it->second.inFlight && !it->second.failed);
|
|
35
|
+
}
|
|
36
|
+
// Called by the sole output consumer AFTER its driver write returns. A
|
|
37
|
+
// reader being empty only proves its last block was pulled, not submitted.
|
|
38
|
+
void didWrite(bool success) {
|
|
39
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
40
|
+
for (auto& [id, source] : sources_) {
|
|
41
|
+
if (source.inFlight && !success) source.failed = true;
|
|
42
|
+
source.inFlight = false;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Mix into existing stereo samples (e.g. oscillator output). Return the
|
|
47
|
+
// frames actually read: a short network packet must not acquire silence
|
|
48
|
+
// padding that lengthens speech and accumulates latency.
|
|
49
|
+
std::size_t mix(std::int16_t* stereo, std::size_t frames) {
|
|
50
|
+
frames = std::min(frames, chunkFrames);
|
|
51
|
+
std::array<std::int16_t, chunkFrames> mono;
|
|
52
|
+
std::size_t available = 0;
|
|
53
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
54
|
+
for (auto& [id, source] : sources_) {
|
|
55
|
+
const auto count = std::min(frames, source.pull(mono.data(), frames));
|
|
56
|
+
source.inFlight = source.inFlight || count > 0;
|
|
57
|
+
available = std::max(available, count);
|
|
58
|
+
for (std::size_t i = 0; i < count; ++i) {
|
|
59
|
+
for (std::size_t channel = 0; channel < 2; ++channel) {
|
|
60
|
+
const auto sum = static_cast<int>(stereo[i * 2 + channel]) + mono[i];
|
|
61
|
+
stereo[i * 2 + channel] = static_cast<std::int16_t>(std::clamp(sum, -32768, 32767));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return available;
|
|
66
|
+
}
|
|
67
|
+
private:
|
|
68
|
+
mutable std::mutex mutex_;
|
|
69
|
+
struct Source { Pull pull; bool inFlight = false; bool failed = false; };
|
|
70
|
+
std::map<Handle, Source> sources_;
|
|
71
|
+
Handle next_ = 0;
|
|
72
|
+
};
|
|
73
|
+
}
|
package/include/canvas.h
CHANGED
|
@@ -88,6 +88,7 @@ public:
|
|
|
88
88
|
// LANDSCAPE is defined (papers3), so every other target is byte-for-byte
|
|
89
89
|
// unaffected. Supports both packed grayscale and native RGB framebuffers.
|
|
90
90
|
void bindPixelsRotatedLandscape(pixel::native_t *physPixels, int logicalWidth, int logicalHeight);
|
|
91
|
+
bool isLandscapeRotated() const { return landscapeRot_; }
|
|
91
92
|
#endif
|
|
92
93
|
|
|
93
94
|
pixel::native_t *pixels();
|
package/include/pixel.h
CHANGED
|
@@ -658,17 +658,51 @@ inline std::uint16_t fromNative(native_t pixel)
|
|
|
658
658
|
#endif
|
|
659
659
|
}
|
|
660
660
|
|
|
661
|
-
//
|
|
662
|
-
//
|
|
663
|
-
//
|
|
664
|
-
//
|
|
661
|
+
// Non-overlapping native pixel spans, including cropped rows whose source and
|
|
662
|
+
// destination have different vector alignment. Keep every load within the span:
|
|
663
|
+
// a scalar head precedes the first aligned-down load, and the scalar tail leaves
|
|
664
|
+
// room for the unaligned vector load's second aligned block.
|
|
665
|
+
inline void copyNative(native_t *dst, const native_t *src, int count)
|
|
666
|
+
{
|
|
667
|
+
if (count <= 0) return;
|
|
668
|
+
#if GEA_PIXEL_PIE_FILL16 && !GEA_PIXEL_FORMAT_IS_8888 && !GEA_PIXEL_FORMAT_IS_GRAY
|
|
669
|
+
if (count >= 32) {
|
|
670
|
+
for (int i = 0; i < 8; ++i) { *dst++ = *src++; --count; }
|
|
671
|
+
while ((reinterpret_cast<std::uintptr_t>(dst) & 15u) != 0) {
|
|
672
|
+
*dst++ = *src++;
|
|
673
|
+
--count;
|
|
674
|
+
}
|
|
675
|
+
const int vectors = (count - 8) >> 3;
|
|
676
|
+
if (vectors > 0) {
|
|
677
|
+
const native_t *cursor = src;
|
|
678
|
+
asm volatile(
|
|
679
|
+
"ee.ld.128.usar.ip q0, %[src], 16\n"
|
|
680
|
+
"loopnez %[vectors], 1f\n"
|
|
681
|
+
"ee.ld.128.usar.ip q1, %[src], 16\n"
|
|
682
|
+
"ee.src.q.qup q2, q0, q1\n"
|
|
683
|
+
"ee.vst.128.ip q2, %[dst], 16\n"
|
|
684
|
+
"ee.orq q0, q1, q1\n"
|
|
685
|
+
"1:\n"
|
|
686
|
+
: [src] "+&r"(cursor), [dst] "+&r"(dst)
|
|
687
|
+
: [vectors] "r"(vectors)
|
|
688
|
+
: "memory");
|
|
689
|
+
src += vectors * 8;
|
|
690
|
+
count -= vectors * 8;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
#endif
|
|
694
|
+
std::memcpy(dst, src, static_cast<std::size_t>(count) * sizeof(native_t));
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Row copies across the RGB565-scratch <-> native-framebuffer boundary. RGB565
|
|
698
|
+
// uses the native span copy above; other framebuffer formats convert per pixel.
|
|
665
699
|
inline void copyNativeToRgb565(std::uint16_t *dst, const native_t *src, int count)
|
|
666
700
|
{
|
|
667
701
|
if (count <= 0) return;
|
|
668
702
|
#if GEA_PIXEL_FORMAT_IS_8888 || GEA_PIXEL_FORMAT_IS_GRAY
|
|
669
703
|
for (int i = 0; i < count; i++) dst[i] = fromNative(src[i]);
|
|
670
704
|
#else
|
|
671
|
-
|
|
705
|
+
copyNative(dst, src, count);
|
|
672
706
|
#endif
|
|
673
707
|
}
|
|
674
708
|
|
|
@@ -678,7 +712,7 @@ inline void copyRgb565ToNative(native_t *dst, const std::uint16_t *src, int coun
|
|
|
678
712
|
#if GEA_PIXEL_FORMAT_IS_8888 || GEA_PIXEL_FORMAT_IS_GRAY
|
|
679
713
|
for (int i = 0; i < count; i++) dst[i] = toNative(src[i]);
|
|
680
714
|
#else
|
|
681
|
-
|
|
715
|
+
copyNative(dst, src, count);
|
|
682
716
|
#endif
|
|
683
717
|
}
|
|
684
718
|
|