Skip to main content
The IFramework service represents the framework of the native game client and provides methods to run code on the game’s main thread safely.

Getting Started

Inject the service into your plugin:
using Dalamud.Plugin;
using Dalamud.Plugin.Services;

public class MyPlugin : IDalamudPlugin
{
    private readonly IFramework framework;

    public MyPlugin(IFramework framework)
    {
        this.framework = framework;
        
        // Subscribe to framework updates
        this.framework.Update += OnFrameworkUpdate;
    }

    private void OnFrameworkUpdate(IFramework framework)
    {
        // Called every frame
        var delta = framework.UpdateDelta;
    }

    public void Dispose()
    {
        this.framework.Update -= OnFrameworkUpdate;
    }
}

Events

Update

Event that gets fired every time the game framework updates.
event OnUpdateDelegate Update
Delegate signature:
public delegate void OnUpdateDelegate(IFramework framework)
Example:
framework.Update += (fw) =>
{
    var timeSinceLastUpdate = fw.UpdateDelta;
    // Update logic here
};
The Update event fires every frame. Be careful with performance-intensive operations.

Properties

LastUpdate

Gets the last time that the Framework Update event was triggered.
DateTime LastUpdate { get; }

LastUpdateUTC

Gets the last time in UTC that the Framework Update event was triggered.
DateTime LastUpdateUTC { get; }

UpdateDelta

Gets the delta between the last Framework Update and the currently executing one.
TimeSpan UpdateDelta { get; }
Example:
var fps = 1.0 / framework.UpdateDelta.TotalSeconds;
chatGui.Print($"Current FPS: {fps:F1}");

IsInFrameworkUpdateThread

Gets a value indicating whether currently executing code is running in the game’s framework update thread.
bool IsInFrameworkUpdateThread { get; }
Example:
if (!framework.IsInFrameworkUpdateThread)
{
    // Need to run on framework thread
    await framework.Run(() => DoGameOperation());
}

IsFrameworkUnloading

Gets a value indicating whether game Framework is unloading.
bool IsFrameworkUnloading { get; }

Methods

Run

Run a given function right away if called from the game’s framework update thread, or otherwise run on the next framework update call.
Task Run(Action action, CancellationToken cancellationToken = default)
Task<T> Run<T>(Func<T> action, CancellationToken cancellationToken = default)
Task Run(Func<Task> action, CancellationToken cancellationToken = default)
Task<T> Run<T>(Func<Task<T>> action, CancellationToken cancellationToken = default)
action
Action | Func<T>
required
Function to call
cancellationToken
CancellationToken
The cancellation token
Returns: Task representing the pending or already completed function. Example:
await framework.Run(() =>
{
    // This runs on the framework thread
    UpdateGameState();
});

var result = await framework.Run(() =>
{
    return GetGameData();
});
Use Run when you need to use await and have your task keep executing on the main thread after waiting.

RunOnFrameworkThread

Run a given function right away if called from the game’s framework update thread, or otherwise run on the next framework update call.
Task<T> RunOnFrameworkThread<T>(Func<T> func)
Task RunOnFrameworkThread(Action action)
func
Func<T> | Action
required
Function to call
Returns: Task representing the pending or already completed function. Example:
var data = await framework.RunOnFrameworkThread(() =>
{
    return ReadGameMemory();
});

await framework.RunOnFrameworkThread(() =>
{
    ModifyGameState();
});
Use RunOnFrameworkThread when you need to call Task.Wait() or Task.Result. It skips the task scheduler if invoked already from the framework thread.

RunOnTick

Run a given function in an upcoming framework tick call, with optional delay.
Task<T> RunOnTick<T>(
    Func<T> func,
    TimeSpan delay = default,
    int delayTicks = default,
    CancellationToken cancellationToken = default)

Task RunOnTick(
    Action action,
    TimeSpan delay = default,
    int delayTicks = default,
    CancellationToken cancellationToken = default)

Task<T> RunOnTick<T>(
    Func<Task<T>> func,
    TimeSpan delay = default,
    int delayTicks = default,
    CancellationToken cancellationToken = default)

Task RunOnTick(
    Func<Task> func,
    TimeSpan delay = default,
    int delayTicks = default,
    CancellationToken cancellationToken = default)
func
Func<T> | Action
required
Function to call
delay
TimeSpan
Wait for given timespan before calling this function
delayTicks
int
Count given number of framework tick calls before calling this function (takes precedence over delay)
cancellationToken
CancellationToken
Cancellation token which will prevent the execution if wait conditions are not met
Returns: Task representing the pending function. Example:
// Run after 5 seconds
await framework.RunOnTick(
    () => chatGui.Print("5 seconds passed!"),
    delay: TimeSpan.FromSeconds(5));

// Run after 60 frames
await framework.RunOnTick(
    () => chatGui.Print("60 frames passed!"),
    delayTicks: 60);

// With cancellation
var cts = new CancellationTokenSource();
await framework.RunOnTick(
    () => DoWork(),
    delay: TimeSpan.FromSeconds(10),
    cancellationToken: cts.Token);

DelayTicks

Returns a task that completes after the given number of ticks.
Task DelayTicks(long numTicks, CancellationToken cancellationToken = default)
numTicks
long
required
Number of ticks to delay
cancellationToken
CancellationToken
The cancellation token
Returns: A task that completes after the specified number of ticks. Example:
// Wait 30 frames
await framework.DelayTicks(30);
chatGui.Print("Waited 30 frames");

// With cancellation
var cts = new CancellationTokenSource();
await framework.DelayTicks(100, cts.Token);

GetTaskFactory

Gets a TaskFactory that runs tasks during Framework Update event.
TaskFactory GetTaskFactory()
Returns: The task factory.

Common Use Cases

Periodic Updates

private TimeSpan timeSinceLastCheck = TimeSpan.Zero;
private readonly TimeSpan checkInterval = TimeSpan.FromSeconds(1);

private void OnFrameworkUpdate(IFramework fw)
{
    timeSinceLastCheck += fw.UpdateDelta;
    
    if (timeSinceLastCheck >= checkInterval)
    {
        timeSinceLastCheck = TimeSpan.Zero;
        PerformPeriodicCheck();
    }
}

Thread-Safe Game Data Access

public async Task<PlayerData> GetPlayerDataAsync()
{
    // Safely read game data from any thread
    return await framework.RunOnFrameworkThread(() =>
    {
        if (!clientState.IsLoggedIn)
            return null;
        
        return new PlayerData
        {
            Name = clientState.LocalPlayer?.Name.ToString(),
            Level = clientState.LocalPlayer?.Level ?? 0,
            CurrentHP = clientState.LocalPlayer?.CurrentHp ?? 0
        };
    });
}

Delayed Execution

public async Task ShowDelayedMessage(string message, float delaySeconds)
{
    await framework.RunOnTick(
        () => chatGui.Print(message),
        delay: TimeSpan.FromSeconds(delaySeconds));
}

public async Task AnimateOverFrames()
{
    for (int i = 0; i < 10; i++)
    {
        await framework.DelayTicks(5);
        UpdateAnimation(i);
    }
}

Conditional Framework Updates

private bool needsUpdate = false;

private void OnFrameworkUpdate(IFramework fw)
{
    if (!needsUpdate)
        return;
    
    if (!clientState.IsLoggedIn)
        return;
    
    UpdateGameOverlay();
    needsUpdate = false;
}

public void RequestUpdate()
{
    needsUpdate = true;
}

Background Work with Framework Sync

public async Task ProcessDataAsync()
{
    // Do heavy work off the framework thread
    var results = await Task.Run(() =>
    {
        return PerformExpensiveCalculation();
    });
    
    // Apply results on the framework thread
    await framework.Run(() =>
    {
        ApplyResultsToGame(results);
    });
}

Frame Rate Monitoring

private readonly Queue<TimeSpan> frameDeltas = new();
private const int SampleSize = 60;

private void OnFrameworkUpdate(IFramework fw)
{
    frameDeltas.Enqueue(fw.UpdateDelta);
    
    if (frameDeltas.Count > SampleSize)
        frameDeltas.Dequeue();
    
    var avgDelta = TimeSpan.FromTicks(
        (long)frameDeltas.Average(d => d.Ticks));
    var avgFps = 1.0 / avgDelta.TotalSeconds;
}

Cancellable Operations

private CancellationTokenSource? operationCts;

public void StartLongOperation()
{
    operationCts = new CancellationTokenSource();
    
    _ = framework.RunOnTick(async () =>
    {
        for (int i = 0; i < 100; i++)
        {
            if (operationCts.Token.IsCancellationRequested)
                break;
            
            ProcessStep(i);
            await framework.DelayTicks(10, operationCts.Token);
        }
    }, cancellationToken: operationCts.Token);
}

public void CancelOperation()
{
    operationCts?.Cancel();
}

Choosing the Right Method

Use Run when:

  • You need to use await in your callback
  • You want continuations to remain on the framework thread
  • You’re writing async code that interacts with the game

Use RunOnFrameworkThread when:

  • You need to call Task.Wait() or access Task.Result
  • You want to skip the task scheduler if already on the framework thread
  • You’re calling from synchronous code

Use RunOnTick when:

  • You need to delay execution
  • You want precise frame-based timing
  • You need cancellation support for delayed operations
Starting new tasks and waiting on them synchronously from framework callbacks will lock up the game. Always use await for asynchronous operations.
Awaiting RunOnFrameworkThread, Run, or RunOnTick directly or indirectly from within the callback passed to these functions can cause deadlocks.

Build docs developers (and LLMs) love