|
|
@ -10,45 +10,68 @@ |
|
|
|
|
|
|
|
|
namespace Service::Time { |
|
|
namespace Service::Time { |
|
|
|
|
|
|
|
|
|
|
|
// Note: this type is not safe for concurrent writes. |
|
|
|
|
|
template <typename T> |
|
|
|
|
|
struct LockFreeAtomicType { |
|
|
|
|
|
u32 counter_; |
|
|
|
|
|
std::array<T, 2> value_; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
template <typename T> |
|
|
|
|
|
static inline void StoreToLockFreeAtomicType(LockFreeAtomicType<T>* p, const T& value) { |
|
|
|
|
|
// Get the current counter. |
|
|
|
|
|
auto counter = p->counter_; |
|
|
|
|
|
|
|
|
|
|
|
// Increment the counter. |
|
|
|
|
|
++counter; |
|
|
|
|
|
|
|
|
|
|
|
// Store the updated value. |
|
|
|
|
|
p->value_[counter % 2] = value; |
|
|
|
|
|
|
|
|
|
|
|
// Fence memory. |
|
|
|
|
|
std::atomic_thread_fence(std::memory_order_release); |
|
|
|
|
|
|
|
|
|
|
|
// Set the updated counter. |
|
|
|
|
|
p->counter_ = counter; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
template <typename T> |
|
|
|
|
|
static inline T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T>* p) { |
|
|
|
|
|
while (true) { |
|
|
|
|
|
// Get the counter. |
|
|
|
|
|
auto counter = p->counter_; |
|
|
|
|
|
|
|
|
|
|
|
// Get the value. |
|
|
|
|
|
auto value = p->value_[counter % 2]; |
|
|
|
|
|
|
|
|
|
|
|
// Fence memory. |
|
|
|
|
|
std::atomic_thread_fence(std::memory_order_acquire); |
|
|
|
|
|
|
|
|
|
|
|
// Check that the counter matches. |
|
|
|
|
|
if (counter == p->counter_) { |
|
|
|
|
|
return value; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
class SharedMemory final { |
|
|
class SharedMemory final { |
|
|
public: |
|
|
public: |
|
|
explicit SharedMemory(Core::System& system_); |
|
|
explicit SharedMemory(Core::System& system_); |
|
|
~SharedMemory(); |
|
|
~SharedMemory(); |
|
|
|
|
|
|
|
|
// TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this? |
|
|
|
|
|
template <typename T, std::size_t Offset> |
|
|
|
|
|
struct MemoryBarrier { |
|
|
|
|
|
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable"); |
|
|
|
|
|
u32_le read_attempt{}; |
|
|
|
|
|
std::array<T, 2> data{}; |
|
|
|
|
|
|
|
|
|
|
|
// These are not actually memory barriers at the moment as we don't have multicore and all |
|
|
|
|
|
// HLE is mutexed. This will need to properly be implemented when we start updating the time |
|
|
|
|
|
// points on threads. As of right now, we'll be updated both values synchronously and just |
|
|
|
|
|
// incrementing the read_attempt to indicate that we waited. |
|
|
|
|
|
void StoreData(u8* shared_memory, T data_to_store) { |
|
|
|
|
|
std::memcpy(this, shared_memory + Offset, sizeof(*this)); |
|
|
|
|
|
read_attempt++; |
|
|
|
|
|
data[read_attempt & 1] = data_to_store; |
|
|
|
|
|
std::memcpy(shared_memory + Offset, this, sizeof(*this)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// For reading we're just going to read the last stored value. If there was no value stored |
|
|
|
|
|
// it will just end up reading an empty value as intended. |
|
|
|
|
|
T ReadData(u8* shared_memory) { |
|
|
|
|
|
std::memcpy(this, shared_memory + Offset, sizeof(*this)); |
|
|
|
|
|
return data[(read_attempt - 1) & 1]; |
|
|
|
|
|
} |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// Shared memory format |
|
|
// Shared memory format |
|
|
struct Format { |
|
|
struct Format { |
|
|
MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint; |
|
|
|
|
|
MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context; |
|
|
|
|
|
MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context; |
|
|
|
|
|
MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction; |
|
|
|
|
|
u32_le format_version; |
|
|
|
|
|
|
|
|
LockFreeAtomicType<Clock::StandardSteadyClockTimePointType> standard_steady_clock_timepoint; |
|
|
|
|
|
LockFreeAtomicType<Clock::SystemClockContext> standard_local_system_clock_context; |
|
|
|
|
|
LockFreeAtomicType<Clock::SystemClockContext> standard_network_system_clock_context; |
|
|
|
|
|
LockFreeAtomicType<bool> is_standard_user_system_clock_automatic_correction_enabled; |
|
|
|
|
|
u32 format_version; |
|
|
}; |
|
|
}; |
|
|
|
|
|
static_assert(offsetof(Format, standard_steady_clock_timepoint) == 0x0); |
|
|
|
|
|
static_assert(offsetof(Format, standard_local_system_clock_context) == 0x38); |
|
|
|
|
|
static_assert(offsetof(Format, standard_network_system_clock_context) == 0x80); |
|
|
|
|
|
static_assert(offsetof(Format, is_standard_user_system_clock_automatic_correction_enabled) == |
|
|
|
|
|
0xc8); |
|
|
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); |
|
|
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size"); |
|
|
|
|
|
|
|
|
void SetupStandardSteadyClock(const Common::UUID& clock_source_id, |
|
|
void SetupStandardSteadyClock(const Common::UUID& clock_source_id, |
|
|
@ -56,10 +79,10 @@ public: |
|
|
void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context); |
|
|
void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context); |
|
|
void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context); |
|
|
void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context); |
|
|
void SetAutomaticCorrectionEnabled(bool is_enabled); |
|
|
void SetAutomaticCorrectionEnabled(bool is_enabled); |
|
|
|
|
|
Format* GetFormat(); |
|
|
|
|
|
|
|
|
private: |
|
|
private: |
|
|
Core::System& system; |
|
|
Core::System& system; |
|
|
Format shared_memory_format{}; |
|
|
|
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
} // namespace Service::Time |
|
|
} // namespace Service::Time |