blob: 88bedea3c2d669319d9e07aadb4ece23722b939b [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_ASYNC_OUTPUT_BUFFER_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_ASYNC_OUTPUT_BUFFER_H_
#include <variant>
#include "lib/fit/function.h"
#include "src/developer/debug/zxdb/console/output_buffer.h"
#include "src/lib/fxl/memory/ref_counted.h"
namespace zxdb {
class Err;
// This class is for collecting formatted output that might be produced in an asynchronous manner
// across many components.
//
// Formatted text (in the form of an OutputBuffer) can be appended or more AsyncOutputBuffers can be
// appended, building up a tree of output. The various parts of this tree can be filled in
// asynchronously and the toplevel buffer's callback will be issued when everything is marked
// complete.
//
// Usage guidelines for general sanity:
//
// - The same code is responsible for Complete()ing an AsyncOutputBuffer as for creating it.
//
// - Don't pass an AsyncOutputBuffer to another function and have the function Complete() it.
//
// - Functions that need async output should generally return an AsyncOutputBuffer that the
// function arranges to be Complete() when possible. Callers can append this to other buffers as
// needed.
//
// - If a function needs to append to an existing AsyncOutputBuffer, pass by raw pointer and do
// not have the function Complete() it. If that function needs to append asynchronously do
// something, it should append a new AsyncOutputBuffer that it will take responsibility for
// Complete()ing.
//
// Example:
//
// // Creates a buffer and appends some text to it.
// auto out = fxl::MakeRefCounted<AsyncOutputBuffer>();
// out->Append("Hello");
//
// // Now have somebody asynchronously append to this placeholder in the
// // middle. This might happen after the next call that append "Goodbye".
// auto sub = fxl::MakeRefCounted<AsyncOutputBuffer>();
// // Could also Append(sub) here, the order won't matter.
// DoSomethingWithACallback(
// [sub](std::string value) {
// sub->Append();
// sub->Complete();
// });
// out->Append(sub);
//
// out->Append("Goodbye");
// out->Complete();
class AsyncOutputBuffer : public fxl::RefCountedThreadSafe<AsyncOutputBuffer> {
public:
using CompletionCallback = fit::callback<void()>;
// Setting the completion callback will assert if the buffer is_complete() because in that case it
// will never be called.
//
// This can only be set to a nonempty function once, but it can be set with an empty function to
// clear it.
void SetCompletionCallback(CompletionCallback cb);
// Returns true if the buffer has been marked complete (there will be no more nodes appended to
// it) and all of the children are also is_complete(). Marking a buffer complete and it having
// complete children are independent events.
bool is_complete() { return pending_resolution_ == 0 && marked_complete_; }
// Mirrors the OutputBuffer API with the addition of being able to append AsyncOutputBuffers.
void Append(std::string str, TextForegroundColor fg = TextForegroundColor::kDefault,
TextBackgroundColor bg = TextBackgroundColor::kDefault);
void Append(fxl::RefPtr<AsyncOutputBuffer> buf);
void Append(Syntax syntax, std::string str);
void Append(OutputBuffer buf);
void Append(const Err& err);
// Call to mark this output buffer complete. This will issue the callback if there is one
// registered. See is_complete() for additional discussion.
//
// Doing additional appends or making it complete again after this call will trigger a debug
// assertion.
void Complete();
// Helper functions that do Append(...) + Complete() since this is a very common use case.
void Complete(std::string str, TextForegroundColor fg = TextForegroundColor::kDefault,
TextBackgroundColor bg = TextBackgroundColor::kDefault);
void Complete(fxl::RefPtr<AsyncOutputBuffer> buf);
void Complete(Syntax syntax, std::string str);
void Complete(OutputBuffer buf);
void Complete(const Err& err);
// Once this buffer is_complete(), the spans and any sub-AsyncOutputBuffers can be flattened into
// one vector.
//
// This operation is destructive so can only be called once. This node and all child nodes will be
// empty after this call.
OutputBuffer DestructiveFlatten();
private:
FRIEND_REF_COUNTED_THREAD_SAFE(AsyncOutputBuffer);
FRIEND_MAKE_REF_COUNTED(AsyncOutputBuffer);
AsyncOutputBuffer() = default;
~AsyncOutputBuffer() = default;
// Called when something happened that could have affected is_complete() to issue the callback.
void CheckComplete();
// Recursive callback for DestructiveFlatten() that destructively moves all spans out of this node
// and its children into the given output buffer.
void DestructiveCollectNodes(OutputBuffer* out);
CompletionCallback completion_callback_;
// Reference count for how many children in nodes_ are uncompleted.
int pending_resolution_ = 0;
// Set when Complete() has been called. This does not necessarily mean that all children have been
// completed (a prerequisite for is_complete().
bool marked_complete_ = false;
// This buffer is a sequence of nodes. A node is either a span of text that's synchronously
// available or an owning reference to another async output buffer that may or may not be filled.
using Span = OutputBuffer::Span;
using Ref = fxl::RefPtr<AsyncOutputBuffer>;
using Node = std::variant<Span, Ref>;
std::vector<Node> nodes_;
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_CONSOLE_ASYNC_OUTPUT_BUFFER_H_