Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/protocol-refresh.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json

name: Protocol refresh

on:
workflow_dispatch:
schedule:
- cron: "23 4 * * 1"

permissions:
contents: read

jobs:
refresh:
name: Refresh the protocol definition
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write

steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7

- name: Setup .NET SDK
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6
with:
dotnet-version: |
10.x
11.0.100-rc.1.26425.128

- name: Read the pinned revision
id: pinned
run: |
set -euo pipefail
{
echo "repository=$(jq -r .repository protocol.lock.json)"
echo "path=$(jq -r .path protocol.lock.json)"
echo "commit=$(jq -r .commit protocol.lock.json)"
} >> "$GITHUB_OUTPUT"

- name: Find the upstream revision
id: upstream
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ steps.pinned.outputs.repository }}
FILE_PATH: ${{ steps.pinned.outputs.path }}
run: |
set -euo pipefail
slug="${REPOSITORY#https://github.com/}"
# The newest commit touching the protocol file, not the newest commit on master:
# most upstream commits do not change it.
commit=$(gh api "repos/${slug}/commits?path=${FILE_PATH}&per_page=1" --jq '.[0].sha')
echo "commit=${commit}" >> "$GITHUB_OUTPUT"

- name: Refresh and regenerate
if: steps.upstream.outputs.commit != steps.pinned.outputs.commit
run: >
dotnet build ObsWebSocket.Core
-t:RefreshObsProtocol
-p:ObsProtocolCommit=${{ steps.upstream.outputs.commit }}
-p:ObsCodegenForceRegeneration=true

- name: Build and test
if: steps.upstream.outputs.commit != steps.pinned.outputs.commit
run: |
set -euo pipefail
dotnet build ObsWebSocket.sln --configuration Release
dotnet test --project "${{ github.workspace }}/ObsWebSocket.Tests/ObsWebSocket.Tests.csproj" \
--configuration Release --no-build -- --filter "TestCategory!=Integration"

- name: Open a pull request
if: steps.upstream.outputs.commit != steps.pinned.outputs.commit
env:
GH_TOKEN: ${{ github.token }}
NEW_COMMIT: ${{ steps.upstream.outputs.commit }}
OLD_COMMIT: ${{ steps.pinned.outputs.commit }}
REPOSITORY: ${{ steps.pinned.outputs.repository }}
run: |
set -euo pipefail
if [ -z "$(git status --porcelain)" ]; then
echo "The refresh produced no diff."
exit 0
fi

branch="protocol-refresh/${NEW_COMMIT:0:12}"
if git ls-remote --exit-code --heads origin "${branch}" >/dev/null 2>&1; then
echo "${branch} already exists."
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git switch -c "${branch}"
git add -A
git commit -m "build: refresh the protocol definition"
git push origin "${branch}"

gh pr create \
--title "build: refresh the protocol definition" \
--body "Pinned revision moves from \`${OLD_COMMIT}\` to \`${NEW_COMMIT}\`.

${REPOSITORY}/compare/${OLD_COMMIT}...${NEW_COMMIT}

Generated with \`ObsCodegenForceRegeneration=true\`, so a change to the definition alone still regenerates. Check the live OBS run before merging: it is the only thing that catches a payload mapped to the wrong field." \
--head "${branch}"
15 changes: 15 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,21 @@ To contribute code, you'll need to set up a local development environment:
```
Exits non-zero on the first failed check.

## Checking the stub types

The stub types have no schema behind them: `protocol.json` types 14 array fields as
`Array<Object>`, so their shapes live only as C++ in obs-websocket. `ObsWebSocket.StubAudit`
diffs ours against those sources and reports fields OBS emits that no stub declares, stub fields
nothing emits, and numerics narrower than the C type behind them.

It needs local clones and is run by hand, so the package builds without an obs-studio checkout:

```bash
dotnet run --project ObsWebSocket.StubAudit -- /path/to/obs-websocket /path/to/obs-studio
```

Exits non-zero when it finds a missing or narrow field.

## Pull Request Process 🚀

1. **Fork the repository** and create your branch from `master`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal static partial class Emitter
/// </summary>
/// <remarks>
/// These are the handles that cost nothing. An event announcing a scene change already says
/// which scene, by uuid, so acting on it needs no lookup and the result is immune to a rename
/// which scene, by uuid, so acting on it costs no extra request and survives a rename
/// that happens between the event arriving and the next request going out. Without them the
/// caller reads <c>e.EventData.SceneName</c> and addresses the scene by name again, which is
/// the round trip and the race the uuid was there to avoid.
Expand Down
22 changes: 22 additions & 0 deletions ObsWebSocket.Core/IObsReconnectDelays.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace ObsWebSocket.Core;

/// <summary>
/// Supplies the delay before each reconnect attempt.
/// </summary>
/// <remarks>
/// Register an implementation after <c>AddObsWebSocketClient</c> to replace the backoff curve
/// built from <see cref="ObsWebSocketClientOptions"/>. The connection loop still decides how many
/// attempts to make and which failures are fatal.
/// </remarks>
public interface IObsReconnectDelays
{
/// <summary>
/// Returns the delay to wait before the retry following <paramref name="retryIndex"/>.
/// </summary>
/// <param name="retryIndex">Zero-based index of the retry about to be made.</param>
/// <param name="cancellationToken">A token to cancel the operation.</param>
ValueTask<TimeSpan> GetDelayAsync(
int retryIndex,
CancellationToken cancellationToken = default
);
}
Loading
Loading