use of io.deephaven.web.client.state.ClientTableState in project deephaven-core by deephaven.
the class JsTable method setState.
@Override
public void setState(final ClientTableState state) {
state.onRunning(s -> {
if (state == currentState) {
lastVisibleState = state;
hasInputTable = s.getTableDef().getAttributes().isInputTable();
// defer the size change so that is there is a viewport sub also waiting for onRunning, it gets it first
LazyPromise.runLater(() -> {
if (state == state()) {
setSize(state.getSize());
}
});
}
}, JsRunnable.doNothing());
final ClientTableState was = currentState;
if (was != state) {
state.onRunning(s -> {
// If already closed, we can ignore this, since we already cleaned those up
if (!isClosed() && was != null && was != state()) {
// if we held a subscription
TableViewportSubscription existingSubscription = subscriptions.remove(was.getHandle());
if (existingSubscription != null && existingSubscription.getStatus() != TableViewportSubscription.Status.DONE) {
JsLog.debug("closing old viewport", state(), existingSubscription.state());
// with the replacement state successfully running, we can shut down the old viewport (unless
// something
// external retained it)
existingSubscription.internalClose();
}
}
}, JsRunnable.doNothing());
boolean historyChanged = false;
if (was != null) {
// check if the new state is derived from the current state
historyChanged = !state.isAncestor(was);
was.pause(this);
JsLog.debug("Table state change (new history? ", historyChanged, ") " + "from ", was.getHandle().toString(), was, " to ", state.getHandle().toString(), state);
}
currentState = state;
ActiveTableBinding active = state.getActiveBinding(this);
if (active == null) {
state.createBinding(this);
} else {
active.changeState(state);
}
if (historyChanged) {
// when the new state is not derived from the current state,
// then, when the new state succeeds, we will totally releaseTable the previous table,
// allowing it to be automatically released (if nobody else needs it).
state.onRunning(success -> {
if (isClosed()) {
// if already closed, we should have already released that handle too
return;
}
if (currentState != state) {
// ancestor
return;
}
final boolean shouldRelease = !state().isAncestor(was);
JsLog.debug("History changing state update complete; release? ", shouldRelease, " state: ", was, LazyString.of(was::toStringMinimal));
if (shouldRelease) {
was.releaseTable(this);
}
}, () -> {
LazyPromise.runLater(() -> {
if (isClosed()) {
// if already closed, we should have already released that handle too
return;
}
if (currentState != state) {
// ancestor
return;
}
final boolean shouldRelease = !currentState.isAncestor(was);
JsLog.debug("History changing state update failed; release? ", shouldRelease, " state: ", was, LazyString.of(was::toStringMinimal));
if (shouldRelease) {
was.releaseTable(this);
}
});
});
}
final CustomEventInit init = CustomEventInit.create();
init.setDetail(state);
fireEvent(INTERNAL_EVENT_STATECHANGED, init);
}
}
use of io.deephaven.web.client.state.ClientTableState in project deephaven-core by deephaven.
the class JsTable method getInternalViewportData.
public Promise<TableData> getInternalViewportData() {
final LazyPromise<TableData> promise = new LazyPromise<>();
final ClientTableState active = state();
active.onRunning(state -> {
if (currentViewportData == null) {
// no viewport data received yet; let's setup a one-shot UPDATED event listener
addEventListenerOneShot(EVENT_UPDATED, ignored -> promise.succeed(currentViewportData));
} else {
promise.succeed(currentViewportData);
}
}, promise::fail, () -> promise.fail("Table closed before viewport data was read"));
return promise.asPromise(MAX_BATCH_TIME);
}
use of io.deephaven.web.client.state.ClientTableState in project deephaven-core by deephaven.
the class JsTable method copy.
@JsMethod
public Promise<JsTable> copy(boolean resolved) {
if (resolved) {
LazyPromise<ClientTableState> promise = new LazyPromise<>();
final ClientTableState unresolved = state();
unresolved.onRunning(promise::succeed, promise::fail, () -> promise.fail("Table failed or closed, copy could not complete"));
return promise.asPromise(MAX_BATCH_TIME).then(s -> Promise.resolve(new JsTable(this)));
}
return Promise.resolve(new JsTable(this));
}
use of io.deephaven.web.client.state.ClientTableState in project deephaven-core by deephaven.
the class WorkerConnection method flush.
private void flush() {
// LATER: instead of running a bunch of serial operations,
// condense these all into a single batch operation.
// All three server calls made by this method are _only_ called by this method,
// so we can reasonably merge all three into a single batched operation.
ArrayList<ClientTableState> statesToFlush = new ArrayList<>(flushable);
flushable.clear();
for (ClientTableState state : statesToFlush) {
if (state.hasNoSubscriptions()) {
// yet completed (we leave orphaned nodes paused until a request completes).
if (state.isSubscribed()) {
state.setSubscribed(false);
if (state.getHandle().isConnected()) {
BiDiStream<FlightData, FlightData> stream = subscriptionStreams.remove(state);
if (stream != null) {
stream.end();
stream.cancel();
}
}
}
if (state.isEmpty()) {
// completely empty; perform release
final ClientTableState.ResolutionState previousState = state.getResolution();
state.setResolution(ClientTableState.ResolutionState.RELEASED);
state.setSubscribed(false);
if (previousState != ClientTableState.ResolutionState.RELEASED) {
cache.release(state);
JsLog.debug("Releasing state", state, LazyString.of(state.getHandle()));
// don't send a release message to the server if the table isn't really there
if (state.getHandle().isConnected()) {
releaseHandle(state.getHandle());
}
}
}
} else {
List<TableSubscriptionRequest> vps = new ArrayList<>();
state.forActiveSubscriptions((table, subscription) -> {
assert table.isActive(state) : "Inactive table has a viewport still attached";
vps.add(new TableSubscriptionRequest(table.getSubscriptionId(), subscription.getRows(), subscription.getColumns()));
});
boolean isViewport = vps.stream().allMatch(req -> req.getRows() != null);
assert isViewport || vps.stream().noneMatch(req -> req.getRows() != null) : "All subscriptions to a given handle must be consistently viewport or non-viewport";
BitSet includedColumns = vps.stream().map(TableSubscriptionRequest::getColumns).reduce((bs1, bs2) -> {
BitSet result = new BitSet();
result.or(bs1);
result.or(bs2);
return result;
}).orElseThrow(() -> new IllegalStateException("Cannot call subscribe with zero subscriptions"));
String[] columnTypes = Arrays.stream(state.getTableDef().getColumns()).map(ColumnDefinition::getType).toArray(String[]::new);
state.setSubscribed(true);
Builder subscriptionReq = new Builder(1024);
double columnsOffset = BarrageSubscriptionRequest.createColumnsVector(subscriptionReq, makeUint8ArrayFromBitset(includedColumns));
double viewportOffset = 0;
if (isViewport) {
viewportOffset = BarrageSubscriptionRequest.createViewportVector(subscriptionReq, serializeRanges(vps.stream().map(TableSubscriptionRequest::getRows).collect(Collectors.toSet())));
}
// TODO #188 support minUpdateIntervalMs
double serializationOptionsOffset = BarrageSubscriptionOptions.createBarrageSubscriptionOptions(subscriptionReq, ColumnConversionMode.Stringify, true, 1000, 0);
double tableTicketOffset = BarrageSubscriptionRequest.createTicketVector(subscriptionReq, state.getHandle().getTicket());
BarrageSubscriptionRequest.startBarrageSubscriptionRequest(subscriptionReq);
BarrageSubscriptionRequest.addColumns(subscriptionReq, columnsOffset);
BarrageSubscriptionRequest.addSubscriptionOptions(subscriptionReq, serializationOptionsOffset);
BarrageSubscriptionRequest.addViewport(subscriptionReq, viewportOffset);
BarrageSubscriptionRequest.addTicket(subscriptionReq, tableTicketOffset);
subscriptionReq.finish(BarrageSubscriptionRequest.endBarrageSubscriptionRequest(subscriptionReq));
FlightData request = new FlightData();
request.setAppMetadata(BarrageUtils.wrapMessage(subscriptionReq, BarrageMessageType.BarrageSubscriptionRequest));
BiDiStream<FlightData, FlightData> stream = this.<FlightData, FlightData>streamFactory().create(headers -> flightServiceClient.doExchange(headers), (first, headers) -> browserFlightServiceClient.openDoExchange(first, headers), (next, headers, c) -> browserFlightServiceClient.nextDoExchange(next, headers, c::apply), new FlightData());
stream.send(request);
stream.onData(new JsConsumer<FlightData>() {
@Override
public void apply(FlightData data) {
ByteBuffer body = typedArrayToLittleEndianByteBuffer(data.getDataBody_asU8());
Message headerMessage = Message.getRootAsMessage(new io.deephaven.javascript.proto.dhinternal.flatbuffers.ByteBuffer(data.getDataHeader_asU8()));
if (body.limit() == 0 && headerMessage.headerType() != MessageHeader.RecordBatch) {
// TODO hang on to the schema to better handle the now-Utf8 columns
return;
}
RecordBatch header = headerMessage.header(new RecordBatch());
BarrageMessageWrapper barrageMessageWrapper = BarrageMessageWrapper.getRootAsBarrageMessageWrapper(new io.deephaven.javascript.proto.dhinternal.flatbuffers.ByteBuffer(data.getAppMetadata_asU8()));
if (barrageMessageWrapper.msgType() == BarrageMessageType.None) {
// continue previous message, just read RecordBatch
appendAndMaybeFlush(header, body);
} else {
assert barrageMessageWrapper.msgType() == BarrageMessageType.BarrageUpdateMetadata;
BarrageUpdateMetadata barrageUpdate = BarrageUpdateMetadata.getRootAsBarrageUpdateMetadata(new io.deephaven.javascript.proto.dhinternal.flatbuffers.ByteBuffer(new Uint8Array(barrageMessageWrapper.msgPayloadArray())));
startAndMaybeFlush(barrageUpdate.isSnapshot(), header, body, barrageUpdate, isViewport, columnTypes);
}
}
private DeltaUpdatesBuilder nextDeltaUpdates;
private void appendAndMaybeFlush(RecordBatch header, ByteBuffer body) {
// using existing barrageUpdate, append to the current snapshot/delta
assert nextDeltaUpdates != null;
boolean shouldFlush = nextDeltaUpdates.appendRecordBatch(header, body);
if (shouldFlush) {
incrementalUpdates(state.getHandle(), nextDeltaUpdates.build());
nextDeltaUpdates = null;
}
}
private void startAndMaybeFlush(boolean isSnapshot, RecordBatch header, ByteBuffer body, BarrageUpdateMetadata barrageUpdate, boolean isViewport, String[] columnTypes) {
if (isSnapshot) {
TableSnapshot snapshot = createSnapshot(header, body, barrageUpdate, isViewport, columnTypes);
// for now we always expect snapshots to arrive in a single payload
initialSnapshot(state.getHandle(), snapshot);
} else {
nextDeltaUpdates = deltaUpdates(barrageUpdate, isViewport, columnTypes);
appendAndMaybeFlush(header, body);
}
}
});
stream.onStatus(err -> {
checkStatus(err);
if (!err.isOk()) {
// TODO (core#1181): fix this hack that enables barrage errors to propagate to the UI widget
state.forActiveSubscriptions((table, subscription) -> {
table.failureHandled(err.getDetails());
});
}
});
BiDiStream<FlightData, FlightData> oldStream = subscriptionStreams.put(state, stream);
if (oldStream != null) {
// cancel any old stream, we presently expect a fresh instance
oldStream.end();
oldStream.cancel();
}
}
}
}
use of io.deephaven.web.client.state.ClientTableState in project deephaven-core by deephaven.
the class RequestBatcher method maybeInsertInterimTable.
private BatchOp maybeInsertInterimTable(BatchOp op, ClientTableState appendTo) {
op.setAppendTo(appendTo);
boolean filterChanged = !appendTo.getFilters().equals(op.getFilters());
if (filterChanged) {
// whenever filters have changed, we will create one exported table w/ filters and any custom columns.
// if there are _also_ sorts that have changed, then we'll want to add those on afterwards.
final List<Sort> appendToSort = appendTo.getSorts();
final List<Sort> desiredSorts = op.getSorts();
boolean sortChanged = !appendToSort.equals(desiredSorts);
if (sortChanged) {
// create an intermediate state with just custom columns and filters.
op.setSorts(appendToSort);
final ClientTableState newAppendTo = insertOp(op, appendTo);
// get a new "next state" to build, which should only cause sorts to be added.
final BatchOp newOp = builder.getOp();
newOp.setSorts(desiredSorts);
newOp.setFilters(op.getFilters());
newOp.setCustomColumns(op.getCustomColumns());
newOp.setFlat(op.isFlat());
newOp.setAppendTo(newAppendTo);
JsLog.debug("Adding interim operation to batch", op, " -> ", newOp);
return newOp;
}
}
return op;
}
Aggregations