snapshot of https://github.com/livekit/livekit.git @ 08c5433449c106943231037d0b892895f6e92d3b (2026-09-15)
This commit is contained in:
197
pkg/sfu/videolayerselector/selectordecisioncache.go
Normal file
197
pkg/sfu/videolayerselector/selectordecisioncache.go
Normal file
@@ -0,0 +1,197 @@
|
||||
// Copyright 2023 LiveKit, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package videolayerselector
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type selectorDecision int
|
||||
|
||||
const (
|
||||
selectorDecisionMissing selectorDecision = iota
|
||||
selectorDecisionDropped
|
||||
selectorDecisionForwarded
|
||||
selectorDecisionUnknown
|
||||
)
|
||||
|
||||
func (s selectorDecision) String() string {
|
||||
switch s {
|
||||
case selectorDecisionMissing:
|
||||
return "MISSING"
|
||||
case selectorDecisionDropped:
|
||||
return "DROPPED"
|
||||
case selectorDecisionForwarded:
|
||||
return "FORWARDED"
|
||||
case selectorDecisionUnknown:
|
||||
return "UNKNOWN"
|
||||
default:
|
||||
return fmt.Sprintf("%d", int(s))
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type SelectorDecisionCache struct {
|
||||
initialized bool
|
||||
base uint64
|
||||
last uint64
|
||||
masks []uint64
|
||||
numEntries uint64
|
||||
numNackEntries uint64
|
||||
|
||||
onExpectEntityChanged map[uint64][]func(entity uint64, decision selectorDecision)
|
||||
}
|
||||
|
||||
func NewSelectorDecisionCache(maxNumElements uint64, numNackEntries uint64) *SelectorDecisionCache {
|
||||
numElements := (maxNumElements*2 + 63) / 64
|
||||
return &SelectorDecisionCache{
|
||||
masks: make([]uint64, numElements),
|
||||
numEntries: numElements * 32, // 2 bits per entry
|
||||
numNackEntries: numNackEntries,
|
||||
onExpectEntityChanged: make(map[uint64][]func(entity uint64, decision selectorDecision)),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) AddForwarded(entity uint64) {
|
||||
s.addEntity(entity, selectorDecisionForwarded)
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) AddDropped(entity uint64) {
|
||||
s.addEntity(entity, selectorDecisionDropped)
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) GetDecision(entity uint64) (selectorDecision, error) {
|
||||
if !s.initialized || entity < s.base {
|
||||
return selectorDecisionMissing, nil
|
||||
}
|
||||
|
||||
if entity > s.last {
|
||||
return selectorDecisionUnknown, nil
|
||||
}
|
||||
|
||||
offset := s.last - entity
|
||||
if offset >= s.numEntries {
|
||||
// asking for something too old
|
||||
return selectorDecisionMissing, fmt.Errorf("too old, oldest: %d, asking: %d", s.last-s.numEntries+1, entity)
|
||||
}
|
||||
|
||||
return s.getEntity(entity), nil
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) ExpectDecision(entity uint64, f func(entity uint64, decision selectorDecision)) bool {
|
||||
if !s.initialized || entity < s.base {
|
||||
return false
|
||||
}
|
||||
|
||||
if entity < s.last {
|
||||
offset := s.last - entity
|
||||
if offset >= s.numEntries {
|
||||
return false // too old
|
||||
}
|
||||
}
|
||||
|
||||
s.onExpectEntityChanged[entity] = append(s.onExpectEntityChanged[entity], f)
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) addEntity(entity uint64, sd selectorDecision) {
|
||||
if !s.initialized {
|
||||
s.initialized = true
|
||||
s.base = entity
|
||||
s.last = entity
|
||||
s.setEntity(entity, sd)
|
||||
return
|
||||
}
|
||||
|
||||
if entity <= s.base {
|
||||
// before base, too old
|
||||
return
|
||||
}
|
||||
|
||||
if entity <= s.last {
|
||||
s.setEntity(entity, sd)
|
||||
return
|
||||
}
|
||||
|
||||
for e := s.last + 1; e != entity; e++ {
|
||||
s.setEntity(e, selectorDecisionUnknown)
|
||||
}
|
||||
|
||||
// update [last+1-nack, entity-nack) to missing
|
||||
missingStart := s.last
|
||||
if missingStart > s.numNackEntries+s.base {
|
||||
missingStart -= s.numNackEntries
|
||||
} else {
|
||||
missingStart = s.base
|
||||
}
|
||||
missingEnd := entity
|
||||
if missingEnd > s.numNackEntries+s.base {
|
||||
missingEnd -= s.numNackEntries
|
||||
} else {
|
||||
missingEnd = s.base
|
||||
}
|
||||
if missingEnd > missingStart {
|
||||
for e := missingStart; e != missingEnd; e++ {
|
||||
s.setEntityIfUnknown(e, selectorDecisionMissing)
|
||||
}
|
||||
}
|
||||
|
||||
s.setEntity(entity, sd)
|
||||
s.last = entity
|
||||
|
||||
for e, fns := range s.onExpectEntityChanged {
|
||||
if e+s.numEntries < s.last {
|
||||
delete(s.onExpectEntityChanged, e)
|
||||
for _, f := range fns {
|
||||
f(e, selectorDecisionMissing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) setEntityIfUnknown(entity uint64, sd selectorDecision) {
|
||||
if s.getEntity(entity) == selectorDecisionUnknown {
|
||||
s.setEntity(entity, sd)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) setEntity(entity uint64, sd selectorDecision) {
|
||||
index, bitpos := s.getPos(entity)
|
||||
s.masks[index] &= ^(0x3 << bitpos) // clear before bitwise OR
|
||||
s.masks[index] |= (uint64(sd) & 0x3) << bitpos
|
||||
|
||||
if sd != selectorDecisionUnknown {
|
||||
if fns, ok := s.onExpectEntityChanged[entity]; ok {
|
||||
delete(s.onExpectEntityChanged, entity)
|
||||
for _, f := range fns {
|
||||
f(entity, sd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) getEntity(entity uint64) selectorDecision {
|
||||
index, bitpos := s.getPos(entity)
|
||||
return selectorDecision((s.masks[index] >> bitpos) & 0x3)
|
||||
}
|
||||
|
||||
func (s *SelectorDecisionCache) getPos(entity uint64) (int, int) {
|
||||
// 2 bits per entity, a uint64 mask can hold 32 entities
|
||||
offset := (entity - s.base) % s.numEntries
|
||||
return int(offset >> 5), int(offset&0x1F) * 2
|
||||
}
|
||||
Reference in New Issue
Block a user