Skip to content

Commit

Permalink
remove pow2 framerate
Browse files Browse the repository at this point in the history
  • Loading branch information
markus-wa committed Jun 6, 2021
1 parent 27e3136 commit 60fa974
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 102 deletions.
35 changes: 27 additions & 8 deletions pkg/demoinfocs/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,22 +447,41 @@ type SayText2 struct {
IsChatAll bool // Seems to always be false, team chat might not be recorded
}

// TickRateInfoAvailable signals that the tick-rate information has been received via CSVCMsg_ServerInfo.
// TickRateSource is the type for TickRateInfo.Source.
// Useful for when you have a preference of which tick-rate you'd like to use over others.
type TickRateSource byte

const (
TickRateSourceHeader TickRateSource = iota
TickRateSourceServerInfo
)

// TickRateInfo signals that the tick-rate information has been received via CSVCMsg_ServerInfo.
// This can be useful for corrupt demo headers where the tick-rate is missing in the beginning of the demo.
type TickRateInfoAvailable struct {
type TickRateInfo struct {
Source TickRateSource
TickRate float64 // See Parser.TickRate()
TickTime time.Duration // See Parser.TickTime()
}

// FrameRateCalibrated signals that the demo's frame rate is available.
// FrameRateSource is the type for FrameRateInfo.Source.
// Useful for when you have a preference of which frame-rate you'd like to use over others.
type FrameRateSource byte

const (
FrameRateSourceHeader FrameRateSource = iota
FrameRateSourceConVars
FrameRateSourceCalibration
)

// FrameRateInfo signals that the demo's frame rate is available.
// This can happen either by reading the demo header,
// or if that is corrupt then once enough frames have passed to calibrate th frame-rate manually.
// See also ParserConfig.FrameRateCalibrationFrames.
type FrameRateCalibrated struct {
FrameRatePow2 float64 // The frame rate as a power of two, this is likely to be more accurate than FrameRateCalculated for most demos
FrameTimePow2 time.Duration // The frame time calculated with FrameRatePow2, this is likely to be more accurate than FrameTimeCalculated for most demos
FrameRateCalculated float64 // FrameRatePow2 might be more accurate
FrameTimeCalculated time.Duration // FrameTimePow2 might be more accurate
type FrameRateInfo struct {
Source FrameRateSource // specifies from where the frame-rate (or the estimation of it) comes. see FrameRateSource.
FrameRate float64
FrameTime time.Duration
}

// ChatMessage signals a player generated chat message.
Expand Down
10 changes: 0 additions & 10 deletions pkg/demoinfocs/fake/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,6 @@ func (p *Parser) FrameTimeCalculated() time.Duration {
return p.Called().Get(0).(time.Duration)
}

// FrameRatePow2 is a mock-implementation of Parser.FrameRatePow2().
func (p *Parser) FrameRatePow2() float64 {
return p.Called().Get(0).(float64)
}

// FrameTimePow2 is a mock-implementation of Parser.FrameTimePow2().
func (p *Parser) FrameTimePow2() time.Duration {
return p.Called().Get(0).(time.Duration)
}

// Progress is a mock-implementation of Parser.Progress().
func (p *Parser) Progress() float32 {
return p.Called().Get(0).(float32)
Expand Down
24 changes: 23 additions & 1 deletion pkg/demoinfocs/net_messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package demoinfocs

import (
"bytes"
"strconv"
"time"

bit "github.com/markus-wa/demoinfocs-golang/v2/internal/bitread"
events "github.com/markus-wa/demoinfocs-golang/v2/pkg/demoinfocs/events"
Expand Down Expand Up @@ -57,16 +59,36 @@ func (p *parser) handleSetConVar(setConVar *msg.CNETMsg_SetConVar) {
p.gameState.rules.conVars[cvar.Name] = cvar.Value
}

// note we should only update the frame rate if it's not determinable from the header
// this is because changing the frame rate mid game has no effect on the active recording
if rate, ok := updated["tv_snapshotrate"]; ok && p.frameRate == 0 {
tvSnapshotRate, err := strconv.Atoi(rate)
if err != nil {
p.setError(err)
} else {
p.setFrameRate(float64(tvSnapshotRate), events.FrameRateSourceConVars)
}
}

p.eventDispatcher.Dispatch(events.ConVarsUpdated{
UpdatedConVars: updated,
})
}

func frameRateInfoAvailableEvent(rate float64, source events.FrameRateSource) events.FrameRateInfo {
return events.FrameRateInfo{
Source: source,
FrameRate: rate,
FrameTime: time.Duration(float64(time.Second) / rate),
}
}

func (p *parser) handleServerInfo(srvInfo *msg.CSVCMsg_ServerInfo) {
// srvInfo.MapCrc might be interesting as well
p.tickInterval = srvInfo.TickInterval

p.eventDispatcher.Dispatch(events.TickRateInfoAvailable{
p.eventDispatcher.Dispatch(events.TickRateInfo{
Source: events.TickRateSourceServerInfo,
TickRate: p.TickRate(),
TickTime: p.TickTime(),
})
Expand Down
40 changes: 7 additions & 33 deletions pkg/demoinfocs/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,7 @@ type parser struct {
userMessageHandler userMessageHandler
eventDispatcher *dp.Dispatcher
currentFrame int // Demo-frame, not ingame-tick
calibratedFrameRate float64 // Calibrated frame-rate for corrupt demo headers, only available after calibration
calibratedFrameRatePow2 float64 // Calibrated frame-rate for corrupt demo headers as power of 2, only available after calibration
frameRate float64 // Calibrated frame-rate for corrupt demo headers, only available after calibration
tickInterval float32 // Duration between ticks in seconds
header *common.DemoHeader // Pointer so we can check for nil
gameState *gameState
Expand Down Expand Up @@ -180,29 +179,18 @@ func legayTickTime(h common.DemoHeader) time.Duration {

// FrameRateCalculated returns the frame rate of the demo (frames aka. demo-ticks per second).
// Not necessarily the tick-rate the server ran on during the game.
// See FrameRatePow2() for a possibly more accurate number.
//
// Returns frame rate from DemoHeader if it's not corrupt.
// Otherwise returns frame rate that has automatically bee calibrated.
// Otherwise returns frame rate that has automatically bee calibrated or read from tv_snapshotrate.
// May also return -1 before calibration has finished.
// See also events.FrameRateCalibrated.
// See also events.FrameRateInfo.
func (p *parser) FrameRateCalculated() float64 {
if p.header != nil && p.header.PlaybackTime != 0 && p.header.PlaybackFrames != 0 {
return legacyFrameRate(*p.header)
}

if p.calibratedFrameRate > 0 {
return p.calibratedFrameRate
}

return -1
}

// FrameRatePow2 returns the frame rate of the demo (frames aka. demo-ticks per second) as a power of 2 (16, 32, 64 ...).
// Returns -1 before calibration has finished.
func (p *parser) FrameRatePow2() float64 {
if p.calibratedFrameRatePow2 > 0 {
return p.calibratedFrameRatePow2
if p.frameRate > 0 {
return p.frameRate
}

return -1
Expand All @@ -213,12 +201,11 @@ func legacyFrameRate(h common.DemoHeader) float64 {
}

// FrameTimeCalculated returns the time a frame / demo-tick takes in seconds.
// See FrameTimePow2() for a possibly more accurate number.
//
// Returns frame time from DemoHeader if it's not corrupt.
// Otherwise returns frame time that has automatically bee calibrated.
// Otherwise returns frame time that has automatically bee calibrated or calculated from tv_snapshotrate.
// May also return -1 before calibration has finished.
// See also events.FrameRateCalibrated.
// See also events.FrameRateInfo.
func (p *parser) FrameTimeCalculated() time.Duration {
if frameRate := p.FrameRateCalculated(); frameRate > 0 {
return time.Duration(float64(time.Second) / frameRate)
Expand All @@ -227,19 +214,6 @@ func (p *parser) FrameTimeCalculated() time.Duration {
return -1
}

// FrameTimePow2 returns the time a frame / demo-tick takes in seconds.
//
// Returns -1 before calibration has finished.
// See also events.FrameRateCalibrated.
// See also FrameRatePow2().
func (p *parser) FrameTimePow2() time.Duration {
if frameRate := p.FrameRatePow2(); frameRate > 0 {
return time.Duration(float64(time.Second) / frameRate)
}

return -1
}

// Progress returns the parsing progress from 0 to 1.
// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
//
Expand Down
19 changes: 4 additions & 15 deletions pkg/demoinfocs/parser_interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,19 @@ type Parser interface {
TickTime() time.Duration
// FrameRateCalculated returns the frame rate of the demo (frames aka. demo-ticks per second).
// Not necessarily the tick-rate the server ran on during the game.
// See FrameRatePow2() for a possibly more accurate number.
//
// Returns frame rate from DemoHeader if it's not corrupt.
// Otherwise returns frame rate that has automatically bee calibrated.
// Otherwise returns frame rate that has automatically bee calibrated or read from tv_snapshotrate.
// May also return -1 before calibration has finished.
// See also events.FrameRateCalibrated.
// See also events.FrameRateInfo.
FrameRateCalculated() float64
// FrameRatePow2 returns the frame rate of the demo (frames aka. demo-ticks per second) as a power of 2 (16, 32, 64 ...).
// Returns -1 before calibration has finished.
FrameRatePow2() float64
// FrameTimeCalculated returns the time a frame / demo-tick takes in seconds.
// See FrameTimePow2() for a possibly more accurate number.
//
// Returns frame time from DemoHeader if it's not corrupt.
// Otherwise returns frame time that has automatically bee calibrated.
// Otherwise returns frame time that has automatically bee calibrated or calculated from tv_snapshotrate.
// May also return -1 before calibration has finished.
// See also events.FrameRateCalibrated.
// See also events.FrameRateInfo.
FrameTimeCalculated() time.Duration
// FrameTimePow2 returns the time a frame / demo-tick takes in seconds.
//
// Returns -1 before calibration has finished.
// See also events.FrameRateCalibrated.
// See also FrameRatePow2().
FrameTimePow2() time.Duration
// Progress returns the parsing progress from 0 to 1.
// Where 0 means nothing has been parsed yet and 1 means the demo has been parsed to the end.
//
Expand Down
55 changes: 38 additions & 17 deletions pkg/demoinfocs/parsing.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,22 @@ func (p *parser) ParseHeader() (common.DemoHeader, error) {

p.header = &h

if h.PlaybackTime > 0 {
if h.PlaybackFrames > 0 {
frameRate := float64(h.PlaybackFrames) / h.PlaybackTime.Seconds()

p.setFrameRate(frameRate, events.FrameRateSourceHeader)
}

if h.PlaybackTicks > 0 {
p.msgDispatcher.Dispatch(events.TickRateInfo{
Source: events.TickRateSourceHeader,
TickRate: p.TickRate(),
TickTime: p.TickTime(),
})
}
}

return h, nil
}

Expand Down Expand Up @@ -102,7 +118,7 @@ func (p *parser) ParseToEnd() (err error) {
err = recoverFromUnexpectedEOF(recover())
}

if errors.Is(err, ErrUnexpectedEndOfDemo) && p.calibratedFrameRate == 0 {
if errors.Is(err, ErrUnexpectedEndOfDemo) && p.frameRate == 0 {
p.calibrateFrameRate()
}
}()
Expand Down Expand Up @@ -176,7 +192,7 @@ func (p *parser) ParseNextFrame() (moreFrames bool, err error) {
err = recoverFromUnexpectedEOF(recover())
}

if errors.Is(err, ErrUnexpectedEndOfDemo) && p.calibratedFrameRate == 0 {
if errors.Is(err, ErrUnexpectedEndOfDemo) && p.frameRate == 0 {
p.calibrateFrameRate()
}
}()
Expand Down Expand Up @@ -226,7 +242,7 @@ func (p *parser) parseFrame() bool {
// Ignore

case dcStop:
if p.calibratedFrameRate == 0 {
if p.frameRate == 0 {
p.calibrateFrameRate()
}

Expand Down Expand Up @@ -397,23 +413,24 @@ func (p *parser) handleFrameParsed(*frameParsedTokenType) {
p.currentFrame++
p.eventDispatcher.Dispatch(events.FrameDone{})

if p.currentFrame >= p.frameRateCalibrationFrames && p.calibratedFrameRate == 0 && p.tickInterval > 0 {
if p.currentFrame >= p.frameRateCalibrationFrames && p.frameRate == 0 && p.tickInterval > 0 {
p.calibrateFrameRate()
}
}

func (p *parser) calibrateFrameRate() {
p.calibratedFrameRate, p.calibratedFrameRatePow2 = calculateFrameRates(p.tickDiffs, p.tickInterval)

p.eventDispatcher.Dispatch(events.FrameRateCalibrated{
FrameRatePow2: p.calibratedFrameRatePow2,
FrameTimePow2: time.Duration(float64(time.Second) / p.calibratedFrameRatePow2),
FrameRateCalculated: p.calibratedFrameRate,
FrameTimeCalculated: time.Duration(float64(time.Second) / p.calibratedFrameRate),
})
rate := calculateFrameRateBestGuess(p.tickDiffs, p.tickInterval)

p.setFrameRate(rate, events.FrameRateSourceCalibration)
}

func calculateFrameRates(tickDiffs map[int]int, tickInterval float32) (frameRate float64, frameRatePow2 float64) {
func (p *parser) setFrameRate(rate float64, source events.FrameRateSource) {
p.frameRate = rate

p.eventDispatcher.Dispatch(frameRateInfoAvailableEvent(rate, source))
}

func calculateFrameRateBestGuess(tickDiffs map[int]int, tickInterval float32) float64 {
const maxTickDiff = 16

var (
Expand All @@ -434,17 +451,21 @@ func calculateFrameRates(tickDiffs map[int]int, tickInterval float32) (frameRate
}

avgTickDiff := float64(diffSum) / float64(countSum)
frameRate = calculateFrameRate(avgTickDiff, tickInterval)
frameRatePow2 = calculateFrameRate(float64(highestCountDiff), tickInterval)
frameRate := calculateFrameRate(avgTickDiff, tickInterval)
frameRatePow2 := calculateFrameRate(float64(highestCountDiff), tickInterval)

return
if float64(int(frameRatePow2)) == frameRatePow2 && uint(frameRatePow2)&uint(frameRatePow2-1) == 0 {
// chances are it's a power of 2 if that's the most frequent diff
return frameRatePow2
}

return frameRate
}

func calculateFrameRate(diff float64, tickInterval float32) float64 {
frameInterval := diff * float64(tickInterval)

return 1.0 / frameInterval

}

/*
Expand Down
Loading

0 comments on commit 60fa974

Please sign in to comment.