jaction
JAction fluent chainable task system for Unity. Triggers on: sequential tasks, delay, timer, repeat loop, WaitUntil, WaitWhile, async workflow, zero-allocation async, coroutine alternative, scheduled action, timed event, polling condition, action sequence, ExecuteAsync, parallel execution
Install
mkdir -p .claude/skills/jaction && curl -L -o skill.zip "https://mcp.directory/api/skills/download/4611" && unzip -o skill.zip -d .claude/skills/jaction && rm skill.zipInstalls to .claude/skills/jaction
About this skill
JAction - Chainable Task Execution
Fluent API for composing complex action sequences in Unity with automatic object pooling, zero-allocation async, and parallel execution support.
When to Use
- Sequential workflows with delays
- Polling conditions (WaitUntil/WaitWhile)
- Repeat loops with intervals
- Game timers and scheduled events
- Zero-GC async operations
- Parallel concurrent executions
Core Concepts
Task Snapshot Isolation
When Execute() or ExecuteAsync() is called, the current task list is snapshotted. Modifications to the JAction after execution starts do NOT affect running executions:
var action = JAction.Create()
.Delay(1f)
.Do(static () => Debug.Log("Original"));
var handle = action.ExecuteAsync();
// This task is NOT executed by the handle above - it was added after the snapshot
action.Do(static () => Debug.Log("Added Later"));
await handle; // Only prints "Original"
This isolation enables safe parallel execution where each handle operates on its own task snapshot.
Return Types
JActionExecution (returned by Execute, awaited from ExecuteAsync):
.Action- The JAction that was executed.Cancelled- Whether THIS specific execution was cancelled.Executing- Whether the action is still executing.Dispose()- Returns JAction to pool
JActionExecutionHandle (returned by ExecuteAsync before await):
.Action- The JAction being executed.Cancelled- Whether this execution is cancelled.Executing- Whether still running.Cancel()- Cancel THIS specific execution.AsUniTask()- Convert toUniTask<JActionExecution>- Awaitable:
await handlereturnsJActionExecution
API Reference
Execution
| Method | Returns | Description |
|---|---|---|
.Execute(timeout) | JActionExecution | Synchronous blocking execution |
.ExecuteAsync(timeout) | JActionExecutionHandle | Async via PlayerLoop (recommended) |
Actions
| Method | Description |
|---|---|
.Do(Action) | Execute synchronous action |
.Do<T>(Action<T>, T) | Execute with state (zero-alloc for reference types) |
.Do(Func<JActionAwaitable>) | Execute async action |
.Do<T>(Func<T, JActionAwaitable>, T) | Async with state |
Timing
| Method | Description |
|---|---|
.Delay(seconds) | Wait specified seconds |
.DelayFrame(frames) | Wait specified frame count |
.WaitUntil(condition, frequency, timeout) | Wait until condition true |
.WaitWhile(condition, frequency, timeout) | Wait while condition true |
Loops
| Method | Description |
|---|---|
.Repeat(action, count, interval) | Repeat N times |
.RepeatWhile(action, condition, frequency, timeout) | Repeat while condition true |
.RepeatUntil(action, condition, frequency, timeout) | Repeat until condition true |
All loop methods have <TState> overloads for zero-allocation with reference types.
Configuration
| Method | Description |
|---|---|
.Parallel() | Enable concurrent execution mode |
.OnCancel(callback) | Register cancellation callback |
.Cancel() | Stop ALL active executions |
.Reset() | Clear state for reuse |
.Dispose() | Return to object pool |
Static Members
| Member | Description |
|---|---|
JAction.Create() | Get pooled instance |
JAction.PooledCount | Check available pooled instances |
JAction.ClearPool() | Empty the pool |
Patterns
Basic Sequence
using var result = await JAction.Create()
.Do(static () => Debug.Log("Step 1"))
.Delay(1f)
.Do(static () => Debug.Log("Step 2"))
.ExecuteAsync();
Always Use using var (CRITICAL)
// CORRECT - auto-disposes and returns to pool
using var result = await JAction.Create()
.Do(() => LoadAsset())
.WaitUntil(() => assetLoaded)
.ExecuteAsync();
// WRONG - memory leak, never returns to pool
await JAction.Create()
.Do(() => LoadAsset())
.ExecuteAsync();
Parallel Execution with Per-Execution Cancellation
var action = JAction.Create()
.Parallel()
.Do(static () => Debug.Log("Start"))
.Delay(5f)
.Do(static () => Debug.Log("Done"));
// Start multiple concurrent executions (each gets own task snapshot)
var handle1 = action.ExecuteAsync();
var handle2 = action.ExecuteAsync();
// Cancel only the first execution
handle1.Cancel();
// Each has independent Cancelled state
var result1 = await handle1; // result1.Cancelled == true
var result2 = await handle2; // result2.Cancelled == false
action.Dispose();
UniTask.WhenAll with Parallel
var action = JAction.Create()
.Parallel()
.Delay(1f)
.Do(static () => Debug.Log("Done"));
var handle1 = action.ExecuteAsync();
var handle2 = action.ExecuteAsync();
await UniTask.WhenAll(handle1.AsUniTask(), handle2.AsUniTask());
action.Dispose();
Zero-Allocation with Reference Types
// CORRECT - static lambda + reference type state = zero allocation
var data = new MyData();
JAction.Create()
.Do(static (MyData d) => d.Process(), data)
.Execute();
// Pass 'this' when inside a class - no wrapper needed
public class Enemy : MonoBehaviour
{
public bool IsStunned;
public void ApplyStun(float duration)
{
IsStunned = true;
JAction.Create()
.Delay(duration)
.Do(static (Enemy self) => self.IsStunned = false, this)
.ExecuteAsync().Forget();
}
}
// Value types use closures (boxing would defeat zero-alloc anyway)
int count = 5;
JAction.Create()
.Do(() => Debug.Log($"Count: {count}"))
.Execute();
Timeout Handling
using var result = await JAction.Create()
.WaitUntil(() => networkReady)
.ExecuteAsync(timeout: 30f);
if (result.Cancelled)
Debug.Log("Timed out!");
Cancellation Callback
var action = JAction.Create()
.OnCancel(() => Debug.Log("Cancelled!"))
.Delay(10f);
var handle = action.ExecuteAsync();
handle.Cancel(); // Triggers OnCancel callback
Game Patterns
Cooldown Timer
public class AbilitySystem
{
public bool CanUse = true;
public async UniTaskVoid TryUseAbility(float cooldown)
{
if (!CanUse) return;
CanUse = false;
PerformAbility();
// Pass 'this' as state - no extra class needed
using var _ = await JAction.Create()
.Delay(cooldown)
.Do(static s => s.CanUse = true, this)
.ExecuteAsync();
}
}
Damage Over Time
public sealed class DoTState
{
public IDamageable Target;
public float DamagePerTick;
}
public static async UniTaskVoid ApplyDoT(
IDamageable target, float damage, int ticks, float interval)
{
var state = JObjectPool.Shared<DoTState>().Rent();
state.Target = target;
state.DamagePerTick = damage;
using var _ = await JAction.Create()
.Repeat(
static s => s.Target?.TakeDamage(s.DamagePerTick),
state, count: ticks, interval: interval)
.ExecuteAsync();
state.Target = null;
JObjectPool.Shared<DoTState>().Return(state);
}
Wave Spawner
public async UniTask RunWaves(WaveConfig[] waves)
{
foreach (var wave in waves)
{
using var result = await JAction.Create()
.Do(() => UI.ShowWaveStart(wave.Number))
.Delay(2f)
.Do(() => SpawnWave(wave))
.WaitUntil(() => ActiveEnemyCount == 0, timeout: 120f)
.Delay(wave.DelayAfter)
.ExecuteAsync();
if (result.Cancelled) break;
}
}
Health Regeneration
public sealed class RegenState
{
public float Health, MaxHealth, HpPerTick;
}
public static async UniTaskVoid StartRegen(RegenState state)
{
using var _ = await JAction.Create()
.RepeatWhile(
static s => s.Health = MathF.Min(s.Health + s.HpPerTick, s.MaxHealth),
static s => s.Health < s.MaxHealth,
state, frequency: 0.1f)
.ExecuteAsync();
}
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Nothing happens | Forgot to call Execute/ExecuteAsync | Add .ExecuteAsync() at the end |
| Memory leak | Missing using var | Always use using var result = await ... |
| Frame drops | Using Execute() | Switch to ExecuteAsync() |
| GC allocations | Closures with reference types | Use static lambda + state parameter |
| Unexpected timing | Value type state | Wrap in reference type or use closure |
| Handle shows wrong Cancelled | Reading after modification | Snapshot is isolated - this is expected |
Common Mistakes
- Missing
using var- Memory leak, JAction never returns to pool - Using
Execute()in production - Blocks main thread, causes frame drops - State overloads with value types - Causes boxing; use closures instead
- Forgetting Execute/ExecuteAsync - Nothing happens
- Heavy work in
.Do()- Callbacks run atomically; keep them lightweight - Using
action.Cancel()in parallel - Cancels ALL executions; usehandle.Cancel()for specific execution - Modifying JAction after ExecuteAsync - Changes don't affect running execution (task snapshot isolation)
More by JasonXuDeveloper
View all skills by JasonXuDeveloper →You might also like
flutter-development
aj-geddes
Build beautiful cross-platform mobile apps with Flutter and Dart. Covers widgets, state management with Provider/BLoC, navigation, API integration, and material design.
drawio-diagrams-enhanced
jgtolentino
Create professional draw.io (diagrams.net) diagrams in XML format (.drawio files) with integrated PMP/PMBOK methodologies, extensive visual asset libraries, and industry-standard professional templates. Use this skill when users ask to create flowcharts, swimlane diagrams, cross-functional flowcharts, org charts, network diagrams, UML diagrams, BPMN, project management diagrams (WBS, Gantt, PERT, RACI), risk matrices, stakeholder maps, or any other visual diagram in draw.io format. This skill includes access to custom shape libraries for icons, clipart, and professional symbols.
godot
bfollington
This skill should be used when working on Godot Engine projects. It provides specialized knowledge of Godot's file formats (.gd, .tscn, .tres), architecture patterns (component-based, signal-driven, resource-based), common pitfalls, validation tools, code templates, and CLI workflows. The `godot` command is available for running the game, validating scripts, importing resources, and exporting builds. Use this skill for tasks involving Godot game development, debugging scene/resource files, implementing game systems, or creating new Godot components.
ui-ux-pro-max
nextlevelbuilder
"UI/UX design intelligence. 50 styles, 21 palettes, 50 font pairings, 20 charts, 8 stacks (React, Next.js, Vue, Svelte, SwiftUI, React Native, Flutter, Tailwind). Actions: plan, build, create, design, implement, review, fix, improve, optimize, enhance, refactor, check UI/UX code. Projects: website, landing page, dashboard, admin panel, e-commerce, SaaS, portfolio, blog, mobile app, .html, .tsx, .vue, .svelte. Elements: button, modal, navbar, sidebar, card, table, form, chart. Styles: glassmorphism, claymorphism, minimalism, brutalism, neumorphism, bento grid, dark mode, responsive, skeuomorphism, flat design. Topics: color palette, accessibility, animation, layout, typography, font pairing, spacing, hover, shadow, gradient."
nano-banana-pro
garg-aayush
Generate and edit images using Google's Nano Banana Pro (Gemini 3 Pro Image) API. Use when the user asks to generate, create, edit, modify, change, alter, or update images. Also use when user references an existing image file and asks to modify it in any way (e.g., "modify this image", "change the background", "replace X with Y"). Supports both text-to-image generation and image-to-image editing with configurable resolution (1K default, 2K, or 4K for high resolution). DO NOT read the image file first - use this skill directly with the --input-image parameter.
fastapi-templates
wshobson
Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
Related MCP Servers
Browse all serversBoost productivity with Task Master: an AI-powered tool for project management and agile development workflows, integrat
Deploy, monitor, and manage cloud based DBMS and cloud database management tasks on Tencent CloudBase with AI-powered to
Automate macOS tasks with AppleScript and JavaScript. Control apps, files, and system efficiently using macOS Automator'
Leverage Graphlit for seamless natural language processing and data retrieval within the Graphlit platform ecosystem.
Easily download videos or convert YouTube to MP3/MP4 with our YouTube downloader for quick content analysis using yt-dlp
TaskManager streamlines project tracking and time management with efficient task queues, ideal for managing projects sof
Stay ahead of the MCP ecosystem
Get weekly updates on new skills and servers.