Several changes make up this commit, which unfortunately
can't be separated:
1) Move decoder stack management from DecoderStack to
DecodeSignal, thereby making DecoderStack unnecessary
2) Change the decoder stack from std::list to an
std::vector for direct decoder access
3) Introduce logic_mux_thread which will take care
of muxing the individual SignalBases' logic data into
(cached) logic data that libsigrokdecode expects.
This is necessary as we can no longer do simple bit
mapping within a single logic data segment's logic
data as we now may feed from multiple logic data
segments at once
4) Refactored the creation of decode traces, making
it more streamlined and flexible while simplifying
the class interface
5) Refactored the auto-assignment of channels
6) Refactored is_decode_signal()
7) Reworked decode signal save/restore, allowing
proper handling and with the decoder stack now
being part of the signal, easier save/restore of
the stack and its settings
For starters, we equip it with some basic wrappers
around the decode stack, rework the annotation signal
a little and use the new DecodeSignal in favor of the
SignalBase class.
The previous implementation supported the selection of an input format
by means of the -I command line option. This commit extends the feature
by adding support for colon separated input format options similar to
sigrok-cli.
This allows users to open files from the command line which previously
became only available after filling in dialogs, and resulted in errors
in the absence of options. Here is an example of how to use the option:
$ pulseview -I csv:header:first-channel=2 -i filename.csv
This fixes bug #951.
This patch was generated using clang-tidy:
clang-tidy -checks="-*,misc-unused-using-decls" -fix
(with manual add-on fixes such as dropping unused headers as well,
and commenting some false-positive cases)
However, don't do this for the StoreSession. Reason is that we
only want to save the original data and not treat any converted
data as its own channel.
Use "using std::foo" to make the actual code itself a lot more readable.
There are some exceptions where we usually cannot do this, e.g. std::thread
often conflicts with "thread" from Qt or Boost.
This patch was generated using clang-tidy:
clang-tidy -checks="-*,modernize-use-emplace" -fix
Using emplace_back() has multiple advantages:
- It's usually shorter and easier to read.
- It's more efficient.
V1: v.push_back("foo");
V2: v.emplace_back("foo");
V1 will construct a temporary std::string from the string literal "foo",
another copy of that temporary object will be constructed and placed
into the vector 'v', then the temporary object's destructor will be called.
V2 will simply create a std::string directly in the vector 'v', i.e.
there's only one construction (not 2) and no destructor needs to be called.
This patch was generated using clang-tidy:
clang-tidy -checks="-*,modernize-make-shared" -fix
(with some additional manual fixups)
Using make_shared() over manual construction has multiple advantages:
- It's shorter to write and easier to read.
V1: auto sb = shared_ptr<Foo>(new Foo());
V2: auto sb = make_shared<Foo>();
- The type "Foo" is repeated less often (less code duplication, lower
risk of forgetting to update one of the "Foo"s upon copy-paste etc.)
- Manual construction leads to two individual allocations (actual data and
the control block of the shared_ptr). Using make_shared() will only lead
to one allocation, which has performance, cache-locality and memory
consumption benefits.
- It's exception-safe, whereas manual construction is not necessarily:
V1: func(shared_ptr<Foo>(new Foo()), shared_ptr<Foo>(new Foo()));
V2: func(make_shared<Foo>(), make_shared<Foo>());
In "V1", one of the "new" invocations could throw, potentially
causing memory leaks. No leaks will happen with make_shared().
Segments allocate chunks of MaxChunkSize bytes each.
Most likely, the last allocated chunk isn't fully used,
so there's memory going to waste. This patch fixes this
by allocating a chunk of the required size that replaces
the last standard chunk.
Previously, PV would run out of storage space for the data
segments because data was stored in a vector. As a vector allows
contiguous access to the underlying data (much like an array),
it needs a contiguous section of memory. With incoming data and
constant resizing of the vector, the OS at some point can no
longer supply such a section of memory, causing PV to abort
acquisition.
This change fixes this by using several chunks that are never
grown in size. Instead, new chunks are allocated and added to
the vector as needed. This way, the OS will be able to provide
memory until it runs out of system memory.