Skip to content

Reader review branch - #1

Draft
JakaMohorko wants to merge 1 commit into
mainfrom
reader-review-branch
Draft

Reader review branch#1
JakaMohorko wants to merge 1 commit into
mainfrom
reader-review-branch

Conversation

@JakaMohorko

Copy link
Copy Markdown
Owner

Review PR

@JakaMohorko JakaMohorko left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will continue from the queue reader onwards in a separate review.

struct DomainInfo
{
std::chrono::system_clock::time_point epoch;
RatioPtr resolution;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could use std::ratio internally. The openDAQ ratio object isn't really the most ideal for calculations.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could but this would mean that we duplicate some functionality that already exist and overload it for std::ratio (seemed like it's not ideal so I haven't commited to that yet).

Comment on lines +143 to +146
using SysPeriod = std::chrono::system_clock::period;
Int scaleNumerator = SysPeriod::num * commonDomain.resolution.getDenominator();
Int scaleDenominator = SysPeriod::den * commonDomain.resolution.getNumerator();
Int offsetFromCommon = epochOffset * scaleNumerator / scaleDenominator;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calculation should be done somewhat carefully to not result in integer overflow. Also, when dividing by scaleDenominator, can this result in a non-integer number?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very unlikely. I think it is reasonable to expect that the later epoch is representible in ticks of common resolution. For example, epochs that are whole seconds satisfy this for all resolutions that are divisors of a unit.

Comment on lines +176 to +183
using SysPeriod = std::chrono::system_clock::period;
Int scaleNumerator = SysPeriod::num * commonDomain.resolution.getDenominator();
Int scaleDenominator = SysPeriod::den * commonDomain.resolution.getNumerator();
Int offsetFromCommon = epochOffset * scaleNumerator / scaleDenominator;
Type valueScaledToCommon = valueInCommon - offsetFromCommon;

Int multiplierNumerator = regularDomain.resolution.getNumerator() * commonDomain.resolution.getDenominator();
Int multiplierDenominator = regularDomain.resolution.getDenominator() * commonDomain.resolution.getNumerator();

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This calculation is duplicated from above if I'm not mistaken. Could be merged into a helper method. It seems like the bottom code is the same.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we could make a helper out of this

Comment on lines +209 to +210
if (den % num != 0) // 1 = k * num/den, the resolution is a fractional divider of a unit
DAQ_THROW_EXCEPTION(NotSupportedException, "Resolution must be aligned on full unit of domain");

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exception here seems like it'll be hard to catch. It might be prudent to at least define new error codes here. If the user receives this error, it'll be hard to debug, especially as this is not thrown in case of bugs, but in case of incompatible signal connections.

It might be worth re-thinking a system for error handling in case of incompatibilities. Throwing exceptions feels a bit overkill.

Comment on lines +269 to +279
// Offset of current domain in common domain ticks
Int epochOffset = domain.epoch.time_since_epoch().count() - commonDomain.epoch.time_since_epoch().count();

using SysPeriod = std::chrono::system_clock::period;
Int scaleNumerator = SysPeriod::num * commonDomain.resolution.getDenominator();
Int scaleDenominator = SysPeriod::den * commonDomain.resolution.getNumerator();
RangeValue offsetFromCommon = static_cast<RangeValue>(epochOffset * scaleNumerator / scaleDenominator);

// tick_common = tick * multiplier
Int multiplierNumerator = domain.resolution.getNumerator() * commonDomain.resolution.getDenominator();
Int multiplierDenominator = domain.resolution.getDenominator() * commonDomain.resolution.getNumerator();

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like a really similar calculation as above again.


BEGIN_NAMESPACE_OPENDAQ

SignalEvent::SignalEvent(const EventPacketPtr& packet)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think merging events is a good approach. I would potentially revisit the reader status API to see if there's a better way of portraying the events instead of forwarding event packets.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool, but with the object having to be an API interface implementation and event packets being canonical, the reason for change would have to be strong.

typeCtx.domainIn = SampleType::Undefined;
typeCtx.domainOut = domainReadType;
typeCtx.valueIn = SampleType::Undefined;
typeCtx.valueOut = mode == ReadMode::RawValue ? SampleType::Undefined : valueReadType;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, we need to clarify how the read mode and value read type interact (and that Unscaled actually does nothing).

Comment on lines +164 to +166
// TODO: Add first timestamp mechanism for sync tolerance checking
checkConnection();
drainConnection();

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a separate step that's potentially called externally? It seems that this needs to be called before any read operation.

The flow will likely be:

  1. User API call
  2. Check "are readers ready?"
  3. Read data with queue readers

Between 2 and 3, we could update the reader state, then read whatever's there. It might even be better that the connection state is not updated within a "read" or "getAvailable" call.

Comment on lines +261 to +265
void QueueReader::checkConnection() const
{
if (!connection.assigned())
DAQ_THROW_EXCEPTION(InvalidOperationException, "Connection must be assigned for this operation.");
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably a placeholder, but the design should be done so that this can not be triggered.

Comment on lines +303 to +306
SizeT QueueReader::getAvailableSamples()
{
return getAvailableSamplesNative() * sampleRateDivider;
}

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned above, I think we're somewhat mixing these calculations and doing them both internally in the queue reader and externally.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user has to allocate buffers in "native" domain. To do this, they will have to use dividers either provided by the reader or calculated manually - this is inconvenient. The reader is only capable of communicating the common domain and a single available count. The only choice here is whether QueueReader does this or the ReadCoordinator will do this for all signals. Being aware of a single divider seems less messy than bookkeeping all dividers to me.

@JakaMohorko
JakaMohorko force-pushed the reader-review-branch branch 2 times, most recently from 1b6c21b to 267a93a Compare July 17, 2026 15:57
@JakaMohorko
JakaMohorko force-pushed the reader-review-branch branch from 267a93a to 353d63e Compare July 17, 2026 15:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants