diff options
Diffstat (limited to 'src/liana')
| -rw-r--r-- | src/liana/client.c | 49 | ||||
| -rw-r--r-- | src/liana/client.h | 8 | ||||
| -rw-r--r-- | src/liana/handlers/codec_client.c | 2 | ||||
| -rw-r--r-- | src/liana/list.c | 28 | ||||
| -rw-r--r-- | src/liana/list.h | 4 | ||||
| -rw-r--r-- | src/liana/meson.build | 11 | ||||
| -rw-r--r-- | src/liana/vcr.c | 73 |
7 files changed, 107 insertions, 68 deletions
diff --git a/src/liana/client.c b/src/liana/client.c index 380ed26..4d5689f 100644 --- a/src/liana/client.c +++ b/src/liana/client.c @@ -136,46 +136,52 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe nn_packet_read_str(packet, &handler); client->duration = nn_packet_read_u64(packet); collect_streams(client, packet); + if (client->streams.count == 0) { + log_warn("Resource has no streams."); + goto out; + } struct lia_prefs *prefs = &client->prefs; u8 selected = 0; - u32 accept_defaults = 0; + u8 accept_defaults = 0; for (; accept_defaults < 2; accept_defaults++) { struct camu_codec_stream *stream; al_array_foreach_ptr(client->streams, i, stream) { - u8 type_mask = 1 << stream->type; - if ((selected & type_mask) || !(prefs->enabled_mask & type_mask)) continue; + u8 type = stream->type; + if (type == CAMU_STREAM_SUBTITLE && client->mask == 0) { + log_warn("Ignoring subtitle-only resource."); + goto out; + } + if ((selected & (1 << type)) || !(prefs->enabled & (1 << type))) { + continue; + } const char *title = NULL; #ifdef CAMU_HAVE_FFMPEG if (stream->mode == CAMU_FFMPEG_COMPAT) { AVDictionary *metadata = stream->av.stream->metadata; const AVDictionaryEntry *title_entry = av_dict_get(metadata, "title", NULL, 0); - if (title_entry) title = title_entry->value; + if (title_entry) { + title = title_entry->value; + } const AVDictionaryEntry *lang_entry = av_dict_get(metadata, "language", NULL, 0); if (lang_entry && !accept_defaults) { u8 lang = CAMU_LANG_UNKNOWN; - str lang_str = al_str_cr(lang_entry->value); - if (al_str_eq(&lang_str, &al_str_c("eng"))) lang = CAMU_LANG_ENGLISH; - else if (al_str_eq(&lang_str, &al_str_c("jpn"))) lang = CAMU_LANG_JAPANESE; - switch (stream->type) { - case CAMU_STREAM_AUDIO: - if (prefs->audio_lang != lang) continue; - break; - case CAMU_STREAM_SUBTITLE: - if (prefs->subtitle_lang != lang) continue; - break; - default: - break; + str s = al_str_cr(lang_entry->value); + if (al_str_eq(&s, &al_str_c("eng"))) lang = CAMU_LANG_ENGLISH; + else if (al_str_eq(&s, &al_str_c("jpn"))) lang = CAMU_LANG_JAPANESE; + if ((type == CAMU_STREAM_AUDIO && lang != prefs->language.audio) || + (type == CAMU_STREAM_SUBTITLE && lang != prefs->language.subtitles)) { + continue; } + break; } } #endif if (title) { - log_info("Selected %s stream (index: %u, title: %s).", stream_type_to_str[stream->type], stream->index, title); + log_info("Selected %s stream (index: %u, title: %s).", stream_type_to_str[type], stream->index, title); } else { - log_info("Selected %s stream (index: %u).", stream_type_to_str[stream->type], stream->index); + log_info("Selected %s stream (index: %u).", stream_type_to_str[type], stream->index); } - selected |= type_mask; - client->mask |= 1 << stream->index; + selected |= (1 << type); struct lia_vcr_track *track = al_alloc_object(struct lia_vcr_track); track->stream = stream; track->client = lia_handler_by_name(&handler)->create_client_handler(); @@ -187,10 +193,12 @@ static void parse_info_packet(struct lia_client *client, struct nn_packet *packe // This will NOT attempt to select another stream. continue; } + client->mask |= 1 << stream->index; client->callback(client->userdata, LIANA_CLIENT_CONFIGURE, stream, track); lia_vcr_add_track(&client->vcr, track); } } +out: client->callback(client->userdata, LIANA_CLIENT_CONFIGURE_COMPLETE, NULL, NULL); } @@ -201,6 +209,7 @@ static void info_packet_callback(void *userdata, struct nn_packet_stream *stream parse_info_packet(client, packet); nn_packet_stream_return_packet(stream, packet); if (client->mask == 0 || lia_vcr_is_empty(&client->vcr)) { + log_warn("Discarding resource with no applicable streams."); al_assert(client->reconnect == RECONNECT_NONE); nn_packet_stream_disconnect(&client->data); return; diff --git a/src/liana/client.h b/src/liana/client.h index 40093e0..681b134 100644 --- a/src/liana/client.h +++ b/src/liana/client.h @@ -27,9 +27,11 @@ struct lia_reconnect_info { }; struct lia_prefs { - u8 enabled_mask; - s8 audio_lang; - s8 subtitle_lang; + u8 enabled; + struct { + u8 audio; + u8 subtitles; + } language; }; struct lia_client { diff --git a/src/liana/handlers/codec_client.c b/src/liana/handlers/codec_client.c index abb5b9a..c3dded1 100644 --- a/src/liana/handlers/codec_client.c +++ b/src/liana/handlers/codec_client.c @@ -33,7 +33,7 @@ static bool codec_client_init(struct lia_client_handler *handler, struct camu_re static bool push_av_packet(struct lia_codec_client *codec, AVPacket *pkt) { s32 ret = codec->dec->push_av_packet(codec->dec, pkt); - if (ret == AVERROR(EAGAIN)) { + while (ret == AVERROR(EAGAIN)) { codec->dec->process(codec->dec); ret = codec->dec->push_av_packet(codec->dec, pkt); } diff --git a/src/liana/list.c b/src/liana/list.c index b47a30c..253ba5f 100644 --- a/src/liana/list.c +++ b/src/liana/list.c @@ -91,6 +91,9 @@ static void unref_all_entries(struct lia_list *list, s32 trigger) } } +// @TODO: When implementing list_remove(), consider this case. +// remove() sequence 0 causes a skip to sequence 1 and sequence 1 fails to load. +// Do we properly move to list->current = -1 and an idle list? static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_entry *entry, s32 sequence, bool *error) { u8 last_load = entry->load; @@ -99,9 +102,6 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e // If sequence is <0 that must mean entry is not yet added to list->entries. if (sequence >= 0) { al_array_remove_at(list->entries, (u32)sequence); - // @TODO: Consider this when implementing list_remove(). - // In the case of remove sequence 0, causing a skip to sequence 1, and sequence 1 - // fails to load, do we properly move to -1 and an idle list. if (list->current > sequence) { struct lia_list_sink *sink; al_array_foreach(list->sinks, i, sink) { @@ -111,6 +111,7 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e list->current--; } struct lia_list_cmd *cmd = list->cmd; + al_assert(cmd); al_assert(cmd->sequence != sequence); if (cmd->sequence > sequence) { cmd->sequence--; @@ -141,6 +142,19 @@ static bool entry_load_and_get_duration(struct lia_list *list, struct lia_list_e static void unload_all_entires(struct lia_list *list) { + struct lia_list_cmd *cmd = list->cmd; + if (cmd && cmd->entry) { + al_assert(cmd->op == ADD); + entry_unload(list, cmd->entry); + list->cmd = NULL; + } + al_array_foreach_rev(list->command_queue, i, cmd) { + if (cmd->entry) { + al_assert(cmd->op == ADD); + entry_unload(list, cmd->entry); + al_array_remove_at(list->command_queue, i); + } + } struct lia_list_entry *entry; al_array_foreach(list->entries, i, entry) { entry_unload(list, entry); @@ -162,6 +176,7 @@ static inline void sink_pause_entry(struct lia_list_sink *sink, struct lia_list_ sink->callback(sink->userdata, LIANA_SINK_PAUSE, entry, sequence, time); } +// @TODO: This should probably give a sequence or ID. static inline void sink_unset_entry(struct lia_list_sink *sink) { sink->callback(sink->userdata, LIANA_SINK_UNSET, NULL, -1, NULL); @@ -341,13 +356,13 @@ static bool handle_skipto(struct lia_list *list, s32 sequence, s32 index) bool error; if (!entry_load_and_get_duration(list, target, index, &error)) { if (error) { - // index might point to a different entry after an error. + // On an error, sequence will address the same entry but index may not. return handle_skipto(list, sequence, index); } return false; } - u64 at = nn_get_timestamp() + LIANA_PAUSE_DELAY; + u64 at = nn_get_timestamp() + LIANA_BASE_PING; u64 pos = target->offset; u8 pause = skipto_entry(current, target, at); @@ -438,6 +453,7 @@ static void handle_toggle_pause(struct lia_list *list, s32 sequence, f64 pts) case LIANA_PAUSE_PAUSE: { al_assert(entry->start != LIANA_TIMESTAMP_INVALID); entry->paused_at = now + LIANA_PAUSE_DELAY; + // @TODO: There is no reason we couldn't pause during a pre-roll. if (entry->paused_at < entry->start) { entry->paused_at = entry->start; } @@ -603,6 +619,8 @@ static bool adjust_for_order_change(struct lia_list *list, struct lia_list_entry } } al_assert(cmd->op == SKIPTO); + // This SKIPTO being in-place is a consideration for the sequence of any + // commands that were queued after this one. return handle_skipto(list, cmd->sequence, cmd->arg0.i); } diff --git a/src/liana/list.h b/src/liana/list.h index 61dcb1d..c8b1d73 100644 --- a/src/liana/list.h +++ b/src/liana/list.h @@ -9,7 +9,7 @@ #define LIANA_SEQUENCE_ANY -1 #define LIANA_BASE_DELAY 450000u // 450ms -#define LIANA_BASE_PING 125000u // 125ms +#define LIANA_BASE_PING 150000u // 150ms #define LIANA_PAUSE_DELAY LIANA_BASE_PING #define LIANA_DELAY_IGNORE 0u @@ -100,7 +100,7 @@ struct lia_list_cmd { struct lia_list_entry *entry; s32 sequence; union { s32 i; u32 u; } arg0; - union { f64 f; u64 l; u32 u; } arg1; + union { u64 l; u32 u; f64 f; } arg1; }; struct lia_list { diff --git a/src/liana/meson.build b/src/liana/meson.build index 5c1cc42..40d4598 100644 --- a/src/liana/meson.build +++ b/src/liana/meson.build @@ -5,27 +5,28 @@ liana_server_src = [ 'handlers.c', 'process.c' ] + liana_client_src = [ 'client.c', 'vcr.c', 'handlers/codec_client.c', 'handlers.c' ] + liana_server_deps = [cache, codecs_server, codecs_client] # codecs_client for transcode. liana_client_deps = [codecs_client] -liana_args = [] -if cache_have_cdio +if 'cdio' in get_option('sources') liana_server_src += ['handlers/cdio_server.c'] liana_client_src += ['handlers/cdio_client.c'] endif -if cache_have_libdvd +if 'libdvd' in get_option('sources') liana_server_src += ['handlers/dvd_server.c'] liana_client_src += ['handlers/dvd_client.c'] endif liana_server = declare_dependency(sources: liana_server_src, - dependencies: liana_server_deps, compile_args: [liana_args, '-DLIANA_SERVER']) + dependencies: liana_server_deps, compile_args: ['-DLIANA_SERVER']) liana_client = declare_dependency(sources: liana_client_src, - dependencies: liana_client_deps, compile_args: [liana_args, '-DLIANA_CLIENT']) + dependencies: liana_client_deps, compile_args: ['-DLIANA_CLIENT']) diff --git a/src/liana/vcr.c b/src/liana/vcr.c index 13082f5..4fa548c 100644 --- a/src/liana/vcr.c +++ b/src/liana/vcr.c @@ -7,9 +7,9 @@ #include "vcr.h" #include "common.h" -#define VCR_BUFFER_BUFFERED MB(4) -#define VCR_BUFFER_GROW_FACTOR 8 -#define VCR_BUFFER_LOW_OFFSET MB(1) +#define VCR_BUFFER_BUFFERED MB(6LL) +#define VCR_BUFFER_GROW_FACTOR 8LL +#define VCR_BUFFER_LOW_OFFSET KB(500LL) AL_STATIC_ASSERT(buf_gt_low_offset, VCR_BUFFER_BUFFERED * VCR_BUFFER_GROW_FACTOR, >, VCR_BUFFER_LOW_OFFSET); enum { @@ -67,12 +67,12 @@ static void update_metrics(struct lia_vcr *vcr, u64 size) u64 diff = now - vcr->metrics.last_report_ts; u64 mark = now - vcr->metrics.last_report_mark; u64 frame = vcr->metrics.current_frame; - if (mark > 500000 || (diff > 2000000 && vcr->metrics.current_frame >= KB(500)) || (!size && frame > 0)) { + if (mark > 750000 || (diff > 5000000 && vcr->metrics.current_frame >= KB(500)) || (!size && frame > 0)) { al_assert(frame > 0); f32 kbps = (frame / 125.f) / (mark / 1000000.f); f32 average_kbps = vcr->metrics.average_kbps; average_kbps = average_kbps == 0.f ? kbps : (average_kbps + kbps) / 2.f; - f32 buffered = al_atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1); + f32 buffered = atomic_load(u64)(&vcr->count, AL_ATOMIC_RELAXED) / (f32)MB(1); f32 capacity = vcr->mark.buffered / (f32)MB(1); log_info("Receiving packets at %.2fkbps (%.2f/%.2fMB).", average_kbps, buffered, capacity); vcr->metrics.average_kbps = average_kbps; @@ -88,9 +88,9 @@ void lia_vcr_init(struct lia_vcr *vcr, struct nn_event_loop *loop, struct nn_pac vcr->data = data; vcr->node_id = node_id; al_array_init(vcr->tracks); - al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); vcr->mark.buffered = VCR_BUFFER_BUFFERED; - al_atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); vcr->expand = VCR_EXPAND_UNTOUCHED; vcr->started = false; #ifndef CAMU_DIRECT_MODE @@ -149,10 +149,10 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata) if (packet) { // Check if we should uncork the packet stream. u32 size = nn_packet_get_size(packet); - u64 buffer = al_atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); + u64 buffer = atomic_sub(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); #ifndef CAMU_DIRECT_MODE - bool buffered = al_atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); - u64 low = al_atomic_load(u64)(&vcr->mark.low, AL_ATOMIC_RELAXED); + bool buffered = atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); + u64 low = atomic_load(u64)(&vcr->mark.low, AL_ATOMIC_RELAXED); if (buffered && (low && buffer <= low)) { nn_signal_send(&vcr->signal); } @@ -176,7 +176,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata) } // Take state again because handle_packet() could have caused the track to be corked. - state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); + state = atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); // We wait if corked (TRACK_STOPPED) or EOF. corked = (packet && state == VCR_TRACK_STOPPED) || !packet; @@ -199,7 +199,7 @@ static nn_thread_result NNWT_THREADCALL vcr_track_thread(void *userdata) nn_cond_wait(&track->cond, &track->mutex); // Check for possibly updated state. - state = al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); + state = atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED); nn_mutex_unlock(&track->mutex); @@ -254,11 +254,11 @@ void lia_vcr_add_track(struct lia_vcr *vcr, struct lia_vcr_track *track) track->vcr = vcr; nn_cond_init(&track->cond); nn_mutex_init(&track->mutex); - al_atomic_store(bool)(&track->buffered, !VCR_TRACK_THREADED(track), AL_ATOMIC_RELAXED); + atomic_store(bool)(&track->buffered, !VCR_TRACK_THREADED(track), AL_ATOMIC_RELAXED); track->running = false; nn_packet_cache_init(&track->cache, 256); al_array_push(vcr->tracks, track); - al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); } bool lia_vcr_remove_track_by_stream(struct lia_vcr *vcr, struct camu_codec_stream *stream) @@ -298,13 +298,13 @@ static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer) bool buffered = true; struct lia_vcr_track *track; al_array_foreach(vcr->tracks, i, track) { - buffered &= al_atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); + buffered &= atomic_load(bool)(&track->buffered, AL_ATOMIC_RELAXED); } if (buffered) { if (vcr->expand == VCR_EXPAND_UNTOUCHED) { vcr->mark.buffered = buffer * VCR_BUFFER_GROW_FACTOR; vcr->expand = VCR_EXPAND_GROWN; - log_info("Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1)); + log_debug("Expanded buffer to size %.2fMB.", vcr->mark.buffered / (f32)MB(1)); return; } if (!vcr->corked) { @@ -316,7 +316,7 @@ static void cork_if_buffered(struct lia_vcr *vcr, u64 buffer) // Don't set low until after we corked so that vcr_track_thread() will never try uncorking // until we know what the low mark is. if (vcr->expand == VCR_EXPAND_GROWN) { - al_atomic_store(u64)(&vcr->mark.low, vcr->mark.buffered - VCR_BUFFER_LOW_OFFSET, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->mark.low, vcr->mark.buffered - VCR_BUFFER_LOW_OFFSET, AL_ATOMIC_RELAXED); vcr->expand = VCR_EXPAND_COMPLETE; } al_array_foreach(vcr->tracks, i, track) { @@ -342,9 +342,15 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) break; } if (VCR_TRACK_THREADED(track)) { - if (nn_packet_cache_send_packet(&track->cache, packet)) { - u64 buffer; - if ((buffer = al_atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED)) >= vcr->mark.buffered) { + u64 buffer = 0; + bool can_send = nn_packet_cache_available(&track->cache); + if (can_send) { + buffer = atomic_add(u64)(&vcr->count, size, AL_ATOMIC_RELAXED); + nn_packet_cache_send_packet(&track->cache, packet); + } + nn_packet_cache_unlock(&track->cache); + if (can_send) { + if (buffer >= vcr->mark.buffered) { #ifndef CAMU_DIRECT_MODE cork_if_buffered(vcr, buffer); #endif @@ -360,12 +366,15 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) } case LIANA_PACKET_EOF: case LIANA_PACKET_ERROR: { + // @TODO: Should ERROR be passed down to LIANA_CLIENT_ERRORED? #ifndef CAMU_DIRECT_MODE update_metrics(vcr, 0); // Flush. #endif - // @TODO: Should ERROR be passed down to LIANA_CLIENT_ERRORED? al_array_foreach(vcr->tracks, i, track) { - nn_packet_cache_send_packet(&track->cache, NULL); + if (nn_packet_cache_available(&track->cache)) { + nn_packet_cache_send_packet(&track->cache, NULL); + } + nn_packet_cache_unlock(&track->cache); } #ifndef CAMU_DIRECT_MODE nn_signal_stop(&vcr->signal); @@ -373,7 +382,7 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) if (op == LIANA_PACKET_EOF) { log_info("Received EOF."); } else if (op == LIANA_PACKET_ERROR) { - log_warn("Forcing EOF because we got an error packet."); + log_warn("Forcing EOF due to an error packet."); } break; } @@ -386,17 +395,17 @@ void lia_vcr_push_packet(struct lia_vcr *vcr, struct nn_packet *packet) void lia_vcr_set_buffered(struct lia_vcr_track *track) { - al_atomic_store(bool)(&track->buffered, true, AL_ATOMIC_RELAXED); + atomic_store(bool)(&track->buffered, true, AL_ATOMIC_RELAXED); } void lia_vcr_cork(struct lia_vcr_track *track) { - al_atomic_store(s32)(&track->state, VCR_TRACK_STOPPED, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_STOPPED, AL_ATOMIC_RELAXED); } void lia_vcr_uncork(struct lia_vcr_track *track) { - if (al_atomic_load(s32)(&track->state, AL_ATOMIC_RELAXED) != VCR_TRACK_STOPPED) { + if (atomic_load(s32)(&track->state, AL_ATOMIC_ACQUIRE) != VCR_TRACK_STOPPED) { // We will get here during normal operation. Early returning is historically // tricky in vcr_uncork(). If I'm understanding correctly, asserting that // cond_is_waiting() just below means we are safe. @@ -404,7 +413,7 @@ void lia_vcr_uncork(struct lia_vcr_track *track) } // Lock before setting track->state to avoid a race with cork(). nn_mutex_lock(&track->mutex); - al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELEASE); al_assert(nn_cond_is_waiting(&track->cond)); nn_cond_signal(&track->cond); nn_mutex_unlock(&track->mutex); @@ -416,7 +425,7 @@ static void vcr_track_close_internal(struct lia_vcr_track *track) // Calling packet_cache_disable() while holding the track mutex can very possibly deadlock. nn_packet_cache_disable(&track->cache); nn_mutex_lock(&track->mutex); - al_atomic_store(s32)(&track->state, VCR_TRACK_CLOSED, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_CLOSED, AL_ATOMIC_RELAXED); if (nn_cond_is_waiting(&track->cond)) { nn_cond_signal(&track->cond); } @@ -451,17 +460,17 @@ void lia_vcr_flush(struct lia_vcr *vcr) struct lia_vcr_track *track; al_array_foreach(vcr->tracks, i, track) { if (VCR_TRACK_THREADED(track)) { - al_atomic_store(bool)(&track->buffered, false, AL_ATOMIC_RELAXED); + atomic_store(bool)(&track->buffered, false, AL_ATOMIC_RELAXED); track->client->flush(track->client); nn_packet_cache_enable(&track->cache); - al_atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); + atomic_store(s32)(&track->state, VCR_TRACK_RUNNING, AL_ATOMIC_RELAXED); } else { track->client->flush(track->client); } } - al_atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->count, 0, AL_ATOMIC_RELAXED); if (vcr->expand == VCR_EXPAND_COMPLETE) { - al_atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); + atomic_store(u64)(&vcr->mark.low, 0, AL_ATOMIC_RELAXED); vcr->expand = VCR_EXPAND_GROWN; } } |