Files
parallel-gx1/Source/Core/InputCommon/ControlReference/ControlReference.cpp
T
LibretroAdmin f7d5095739 Core, InputCommon, VideoCommon: remove the remaining locks
Every std::mutex, lock_guard, unique_lock, scoped_lock,
condition_variable, call_once and semaphore left in Source/Core is
replaced by a lock-free design:

- Input: the device list, control bindings, stick calibration and
  default device are published snapshots read under a quiesce; a
  rebind requested while another thread is rebinding is handed to
  that thread instead of waited for.
- WiimoteReal: the pool thread owns the slots and runs commands from
  an MPSC queue; the active Wiimotes are atomic slots.
- EXI/SI/IOS devices: socket and packet traffic goes through SPSC
  rings or MPSC handoffs owned by one thread; USB scanning publishes
  its device list, and libusb transfers drain through a CallGate.
- Memory cards: the flush thread applies change records queued by
  the CPU thread.
- Custom assets: loads are scheduled as a published batch with an
  atomic cursor, results come back over an MPSC stack, and busy
  assets are skipped and retried.
- D3D state and GL program caches use insert-only atomic maps
  (Common/AtomicHashMap.h).
- Logging appends with O_APPEND / FILE_APPEND_DATA; FatFs reports
  busy instead of waiting; per-socket WSAStartup/WSACleanup.

Deleted as unused by the libretro core: the profiler, the FIFO
recorder, the volume verifier, the touch InputOverrider and the
GameCube adapter's libusb path.
2026-11-05 20:11:31 +01:00

174 lines
4.3 KiB
C++

// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "InputCommon/ControlReference/ControlReference.h"
#include <cstring>
#include "Common/CommonTypes.h"
using namespace ciface::ExpressionParser;
static thread_local bool tls_input_gate = true;
void ControlReference::SetInputGate(bool enable)
{
tls_input_gate = enable;
}
bool ControlReference::GetInputGate()
{
return tls_input_gate;
}
//
// UpdateReference
//
// Updates a controlreference's binded devices/controls
// need to call this to re-bind a control reference after changing its expression
//
void ControlReference::FreeBinding(quiesce_node_t* node)
{
delete reinterpret_cast<Binding*>(node);
}
void ControlReference::Publish(Binding* binding)
{
retro_atomic_store_release_int(&m_has_expression, !binding->expression.empty());
if (void* old = retro_atomic_exchange_ptr(&m_binding, binding))
quiesce_retire(ciface::InputQuiesce(), &static_cast<Binding*>(old)->node, FreeBinding);
}
void ControlReference::UpdateReference(ciface::ExpressionParser::ControlEnvironment& env)
{
ciface::InputReadScope scope;
for (;;)
{
Binding* const cur = PeekBinding();
if (!cur || !cur->parsed)
return;
// A fresh parse to bind, leaving the published one untouched for its readers.
auto parse_result = ParseExpression(cur->expression);
auto* next = new Binding{{}, cur->expression, parse_result.status, std::move(parse_result.expr)};
if (next->parsed)
next->parsed->UpdateReferences(env);
// Lost to a SetExpression or another rebind meanwhile: bind what is there now.
if (retro_atomic_cas_ptr(&m_binding, cur, next))
{
quiesce_retire(ciface::InputQuiesce(), &cur->node, FreeBinding);
return;
}
delete next;
}
}
int ControlReference::BoundCount() const
{
ciface::InputReadScope scope;
const Binding* const b = PeekBinding();
return b && b->parsed ? b->parsed->CountNumControls() : 0;
}
ParseStatus ControlReference::GetParseStatus() const
{
ciface::InputReadScope scope;
const Binding* const b = PeekBinding();
return b ? b->status : ParseStatus::EmptyExpression;
}
std::string ControlReference::GetExpression() const
{
ciface::InputReadScope scope;
const Binding* const b = PeekBinding();
return b ? b->expression : std::string{};
}
bool ControlReference::HasExpression() const
{
return retro_atomic_load_acquire_int(&m_has_expression) != 0;
}
std::optional<std::string> ControlReference::SetExpression(std::string expr)
{
auto parse_result = ParseExpression(expr);
Publish(new Binding{{}, std::move(expr), parse_result.status, std::move(parse_result.expr)});
return parse_result.description;
}
ControlState ControlReference::GetRange() const
{
const u64 bits = static_cast<u64>(retro_atomic_load_acquire_64(&m_range_bits));
ControlState range;
std::memcpy(&range, &bits, sizeof(range));
return range;
}
void ControlReference::SetRange(ControlState range)
{
u64 bits;
std::memcpy(&bits, &range, sizeof(bits));
retro_atomic_store_release_64(&m_range_bits, static_cast<int64_t>(bits));
}
ControlReference::ControlReference()
{
SetRange(1.0);
}
ControlReference::~ControlReference()
{
delete static_cast<Binding*>(retro_atomic_exchange_ptr(&m_binding, nullptr));
}
InputReference::InputReference() : ControlReference()
{
}
OutputReference::OutputReference() : ControlReference()
{
}
bool InputReference::IsInput() const
{
return true;
}
bool OutputReference::IsInput() const
{
return false;
}
//
// InputReference :: State
//
// Gets the state of an input reference
// override function for ControlReference::State ...
//
ControlState InputReference::State(const ControlState ignore)
{
if (!GetInputGate())
return 0.0;
ciface::InputReadScope scope;
const Binding* const b = PeekBinding();
return b && b->parsed ? b->parsed->GetValue() * GetRange() : 0.0;
}
//
// OutputReference :: State
//
// Set the state of all binded outputs
// overrides ControlReference::State .. combined them so I could make the GUI simple / inputs ==
// same as outputs one list
// I was lazy and it works so watever
//
ControlState OutputReference::State(const ControlState state)
{
ciface::InputReadScope scope;
const Binding* const b = PeekBinding();
if (b && b->parsed)
b->parsed->SetValue(state * GetRange());
return 0.0;
}