diff --git a/README.md b/README.md index 12ca1a1..b9ddf95 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ The bot requires the following permissions: * Voice Channel Speak * Voice Channel Use Voice Activity +Permission integer 36768768. + ### Finding Discord CID and GID Discord GID is a unique ID linked to one Discord Server, also called Guild. CID is similarly a unique ID for a Discord Channel. To find these you need to set Discord into developer Mode. diff --git a/internal/bridge/discord.go b/internal/bridge/discord.go index 0ccc666..52c5e30 100644 --- a/internal/bridge/discord.go +++ b/internal/bridge/discord.go @@ -99,7 +99,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, default: } - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(true) if (len(pcm) > 1 && streaming) || (len(pcm) > dd.Bridge.BridgeConfig.DiscordStartStreamingCount && !streaming) { if !streaming { @@ -135,7 +135,7 @@ func (dd *DiscordDuplex) discordSendPCM(ctx context.Context, wg *sync.WaitGroup, // We want to do this after alerting the user of possible short speaking cycles for i := 0; i < 5; i++ { internalSend(opusSilence) - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(false) } dd.Bridge.DiscordVoice.Speaking(false) @@ -234,7 +234,7 @@ func (dd *DiscordDuplex) discordReceivePCM(ctx context.Context, wg *sync.WaitGro dd.fromDiscordMap[p.SSRC] = s dd.discordMutex.Unlock() - p.PCM, err = s.decoder.Decode(p.Opus, deltaT*2, false) + p.PCM, err = s.decoder.Decode(p.Opus, deltaT, false) if err != nil { OnError("Error decoding opus data", err) continue @@ -282,7 +282,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou default: } - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(true) dd.discordMutex.Lock() @@ -354,7 +354,7 @@ func (dd *DiscordDuplex) fromDiscordMixer(ctx context.Context, wg *sync.WaitGrou for i := 0; i < 5; i++ { mumbleTimeoutSend(mumbleSilence) - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(false) } toMumbleStreaming = false diff --git a/internal/bridge/mumble.go b/internal/bridge/mumble.go index be3edd8..8c037ec 100644 --- a/internal/bridge/mumble.go +++ b/internal/bridge/mumble.go @@ -61,7 +61,7 @@ func (m MumbleDuplex) fromMumbleMixer(ctx context.Context, wg *sync.WaitGroup, t default: } - sleepTick.SleepNextTarget() + sleepTick.SleepNextTarget(true) mutex.Lock() diff --git a/pkg/sleepct/sleepct.go b/pkg/sleepct/sleepct.go index 8a82d0a..08ae4e8 100644 --- a/pkg/sleepct/sleepct.go +++ b/pkg/sleepct/sleepct.go @@ -2,19 +2,21 @@ package sleepct import ( "fmt" - "sync" "time" ) -// SleepCT - Sleep constant time step crates a sleep based ticker -// designed maintain a sleep/tick interval +// SleepCT - Sleep constant time step crates a sleep based ticker. +// designed maintain a consistent sleep/tick interval. +// The sleeper can be paused waiting to be singaled from another go routine. +// This allows for the pausing of loops that do not have work to complete type SleepCT struct { - sync.Mutex - d time.Duration // duration - t time.Time // last time target + d time.Duration // desired duration between targets + t time.Time // last time target + resume chan bool } func (s *SleepCT) Start(d time.Duration) { + s.resume = make(chan bool, 1) if s.t.IsZero() { s.d = d s.t = time.Now() @@ -23,28 +25,49 @@ func (s *SleepCT) Start(d time.Duration) { } } -func (s *SleepCT) SleepNextTarget() { - s.Lock() - - now := time.Now() - +// Sleep to the next target duration. +// If pause it set to true will sleep the duration and wait to be notified. +// The notification channel will be cleared when the thread wakes. +// SleepNextTarget should not be call more than once concurrently. +func (s *SleepCT) SleepNextTarget(pause bool) { var last time.Time if s.t.IsZero() { fmt.Println("SleepCT reset") - last = now.Add(-s.d) + last = time.Now().Add(-s.d) } else { last = s.t } - // Next Target + // Sleep to Next Target s.t = last.Add(s.d) - d := s.t.Sub(now) + d := time.Until(s.t) time.Sleep(d) - // delta := now.Sub(s.t) + // delta := time.Since(s.t) // fmt.Println("delta", delta, d, time.Since(s.t)) - s.Unlock() + if pause { + // wait until resume + if len(s.resume) == 0 { + <-s.resume + // if we did pause set the last sleep target to now + last = time.Now() + } + } + + // Drain the resume channel + select { + case <-s.resume: + default: + } +} + +// Notify attempts to resume a paused sleeper. +// It is safe to call notify from other processes and as often as desired. +func (s *SleepCT) Notify() { + select { + case s.resume <- true: + } } diff --git a/test/timing_test.go b/test/timing_test.go index d3cfdcb..7985e85 100644 --- a/test/timing_test.go +++ b/test/timing_test.go @@ -13,12 +13,12 @@ import ( "github.com/stieneee/tickerct" ) -const testCount int64 = 10000 +const testCount int64 = 1000 const maxSleepInterval time.Duration = 15 * time.Millisecond const tickerInterval time.Duration = 10 * time.Millisecond const testDuration time.Duration = time.Duration(testCount * 10 * int64(time.Millisecond)) -func testTickerBaseCase(wg *sync.WaitGroup) { +func testTickerBaseCase(wg *sync.WaitGroup, test *testing.T) { wg.Add(1) go func(interval time.Duration) { now := time.Now() @@ -39,7 +39,7 @@ func testTickerBaseCase(wg *sync.WaitGroup) { func TestTickerBaseCase(t *testing.T) { wg := sync.WaitGroup{} - testTickerBaseCase(&wg) + testTickerBaseCase(&wg, t) wg.Wait() } @@ -115,7 +115,7 @@ func testSleepCT(wg *sync.WaitGroup) { if i+1 < testCount { time.Sleep(time.Duration(float64(maxSleepInterval) * rand.Float64())) } - s.SleepNextTarget() + s.SleepNextTarget(false) } fmt.Println("SleepCT (loaded) after", testDuration, "drifts", time.Since(start)-testDuration) wg.Done() @@ -130,6 +130,35 @@ func TestSleepCT(t *testing.T) { wg.Wait() } +func testSleepCTPause(wg *sync.WaitGroup) { + wg.Add(1) + go func(interval time.Duration) { + now := time.Now() + start := now + // start the ticker + s := sleepct.SleepCT{} + s.Start(interval) + var i int64 + for i = 0; i < testCount; i++ { + if i+1 < testCount { + time.Sleep(time.Duration(float64(maxSleepInterval) * rand.Float64())) + } + s.Notify() + s.SleepNextTarget(true) + } + fmt.Println("SleepCT Pause (loaded) after", testDuration, "drifts", time.Since(start)-testDuration) + wg.Done() + }(tickerInterval) +} + +func TestSleepCTPause(t *testing.T) { + wg := sync.WaitGroup{} + + testSleepCTPause(&wg) + + wg.Wait() +} + func TestIdleJitter(t *testing.T) { wg := sync.WaitGroup{}