Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04334691d0
|
||
|
|
ebd5dafa2d
|
63
.omo/boulder.json
Normal file
63
.omo/boulder.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"schema_version": 2,
|
||||
"active_work_id": "api-auth-fix-fb81d2ac",
|
||||
"works": {
|
||||
"api-auth-fix-fb81d2ac": {
|
||||
"work_id": "api-auth-fix-fb81d2ac",
|
||||
"active_plan": "/mnt/d/Projects/ichni_Official/.omo/plans/api-auth-fix.md",
|
||||
"plan_name": "api-auth-fix",
|
||||
"status": "active",
|
||||
"started_at": "2026-06-17T09:10:43.670Z",
|
||||
"updated_at": "2026-06-17T09:22:23.705Z",
|
||||
"session_ids": [
|
||||
"opencode:ses_12b516164ffekTIOv7bJsDWp4s"
|
||||
],
|
||||
"session_origins": {
|
||||
"opencode:ses_12b516164ffekTIOv7bJsDWp4s": "direct"
|
||||
},
|
||||
"agent": "atlas",
|
||||
"task_sessions": {
|
||||
"todo:1": {
|
||||
"task_key": "todo:1",
|
||||
"task_label": "1",
|
||||
"task_title": "Fix ApiResponse.cs + ApiClient.cs — GlobalResponse<T> camelCase + BaseUrl port",
|
||||
"session_id": "opencode:ses_12b25b8bcffeXO77tLYyh5N2BA",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "quick",
|
||||
"updated_at": "2026-06-17T09:22:23.705Z",
|
||||
"started_at": "2026-06-17T09:20:19.050Z",
|
||||
"status": "completed",
|
||||
"ended_at": "2026-06-17T09:22:23.705Z",
|
||||
"elapsed_ms": 124655
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"active_plan": "/mnt/d/Projects/ichni_Official/.omo/plans/api-auth-fix.md",
|
||||
"started_at": "2026-06-17T09:10:43.670Z",
|
||||
"status": "active",
|
||||
"updated_at": "2026-06-17T09:22:23.705Z",
|
||||
"session_ids": [
|
||||
"opencode:ses_12b516164ffekTIOv7bJsDWp4s"
|
||||
],
|
||||
"session_origins": {
|
||||
"opencode:ses_12b516164ffekTIOv7bJsDWp4s": "direct"
|
||||
},
|
||||
"plan_name": "api-auth-fix",
|
||||
"task_sessions": {
|
||||
"todo:1": {
|
||||
"task_key": "todo:1",
|
||||
"task_label": "1",
|
||||
"task_title": "Fix ApiResponse.cs + ApiClient.cs — GlobalResponse<T> camelCase + BaseUrl port",
|
||||
"session_id": "opencode:ses_12b25b8bcffeXO77tLYyh5N2BA",
|
||||
"agent": "Sisyphus-Junior",
|
||||
"category": "quick",
|
||||
"updated_at": "2026-06-17T09:22:23.705Z",
|
||||
"started_at": "2026-06-17T09:20:19.050Z",
|
||||
"status": "completed",
|
||||
"ended_at": "2026-06-17T09:22:23.705Z",
|
||||
"elapsed_ms": 124655
|
||||
}
|
||||
},
|
||||
"agent": "atlas"
|
||||
}
|
||||
316
.omo/plans/api-auth-fix.md
Normal file
316
.omo/plans/api-auth-fix.md
Normal file
@@ -0,0 +1,316 @@
|
||||
# API Auth Access Fix — Unity Client
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Quick Summary**: Fix 3 critical bugs preventing the Unity client from successfully authenticating with the IchniOnline backend: wrong server port, GlobalResponse<T> field name mismatch (PascalCase vs camelCase) causing silent deserialization failure, and null pointer in third-party login flow.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - `ApiResponse.cs` + `ApiClient.cs` — Atomic fix: fields renamed to camelCase, BaseUrl port corrected
|
||||
> - `AuthService.cs` — null check added for third-party login response
|
||||
>
|
||||
> **Estimated Effort**: Quick (3 files, targeted changes)
|
||||
> **Parallel Execution**: YES — all 3 tasks can run in parallel (independent files)
|
||||
> **Critical Path**: None (no dependencies between tasks)
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
用户要求结合后端工程和 Apifox API 文档来对 `Assets/Scripts/Online` 目录里的 API 访问进行检查和修复,仅处理认证相关接口。
|
||||
|
||||
### Interview Summary
|
||||
**Key Discussions**:
|
||||
- Only auth-related endpoints in scope: login, register, session-key, third-party login
|
||||
- Ignore beatmap and other APIs for now
|
||||
- Only modify files within `Assets/Scripts/Online/`
|
||||
|
||||
**Research Findings**:
|
||||
- Server (ASP.NET Core) uses `System.Text.Json` with camelCase policy → JSON keys: `code`, `message`, `data`
|
||||
- Unity client uses `JsonUtility.FromJson` which is **case-sensitive** → C# field names must exactly match JSON keys
|
||||
- Current `GlobalResponse<T>` has PascalCase `Code`/`Message`/`Data` → **silent deserialization failure**
|
||||
- Server runs on `http://localhost:5308` (launchSettings.json) but Unity client has `http://localhost:60887` → **all requests fail**
|
||||
- Third-party login can return `data: null` (unbound account) → **NullReferenceException** when accessing `result.Data.token`
|
||||
|
||||
### Metis Review
|
||||
*Skipped — clear scope, direct bugs, user confirmed approach.*
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
Fix the Unity client's auth API access so that all 4 auth endpoints (session-key, login, register, third-party login) successfully communicate with the IchniOnline backend.
|
||||
|
||||
### Concrete Deliverables
|
||||
- `Assets/Scripts/Online/Network/Models/ApiResponse.cs` — GlobalResponse field names corrected
|
||||
- `Assets/Scripts/Online/Network/ApiClient.cs` — BaseUrl port fixed, field access updated
|
||||
- `Assets/Scripts/Online/Logic/AuthService.cs` — null-safe third-party login handling
|
||||
|
||||
### Definition of Done
|
||||
- `IchniOnlineApiClient.Instance.BaseUrl` returns `http://localhost:5308`
|
||||
- `JsonUtility.FromJson` correctly populates `GlobalResponse<T>.code`/`message`/`data`
|
||||
- Third-party login with unbound account does not throw NullReferenceException
|
||||
- All 3 auth flows (password login, TapTap login, register) return correct `ApiResult`
|
||||
|
||||
### Must Have
|
||||
- Fix BaseUrl port from 60887 to 5308
|
||||
- Fix `GlobalResponse<T>` field names to match JSON camelCase
|
||||
- Add null check for `result.Data` in third-party login flow
|
||||
- All changes within `Assets/Scripts/Online/` only
|
||||
|
||||
### Must NOT Have (Guardrails)
|
||||
- Do NOT touch beatmap or non-auth API code
|
||||
- Do NOT modify files outside `Assets/Scripts/Online/`
|
||||
- Do NOT add Newtonsoft.Json or other new dependencies
|
||||
- Do NOT refactor architecture (stay minimal, targeted fixes)
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy (MANDATORY)
|
||||
|
||||
> **ZERO HUMAN INTERVENTION** — ALL verification is agent-executed.
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: NO
|
||||
- **Automated tests**: None
|
||||
- **Framework**: N/A
|
||||
|
||||
### QA Policy
|
||||
Every task MUST include agent-executed QA scenarios. Evidence saved to `.omo/evidence/task-{N}-{scenario-slug}.{ext}`.
|
||||
|
||||
- **Library/Module verification**: Use Bash (bun/node REPL style) with `script-execute` to run C# test snippets that directly verify `JsonUtility.FromJson` deserialization behavior
|
||||
- **Code review verification**: Read modified files and verify correctness by cross-referencing with server API contracts
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
```
|
||||
Wave 1 (Start Immediately):
|
||||
├── Task 1: Fix ApiResponse.cs + ApiClient.cs — GlobalResponse camelCase + BaseUrl port [quick]
|
||||
└── Task 2: Fix AuthService.cs — third-party login null check [quick]
|
||||
|
||||
Wave FINAL (After ALL tasks):
|
||||
├── Task F1: QA verification (oracle)
|
||||
└── Task F2: Scope fidelity check (deep)
|
||||
|
||||
Critical Path: Task 1 → F1, F2 (independent from Task 2)
|
||||
Parallel Speedup: ~50% faster than sequential (Task 1 and Task 2 run in parallel)
|
||||
Max Concurrent: 2 (both in Wave 1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. Fix ApiResponse.cs + ApiClient.cs — GlobalResponse<T> camelCase + BaseUrl port
|
||||
|
||||
**What to do**:
|
||||
- In `ApiResponse.cs`: Rename field `Code` → `code`, `Message` → `message`, `Data` → `data`
|
||||
- In `ApiClient.cs`:
|
||||
- Change `BaseUrl` default from `"http://localhost:60887"` to `"http://localhost:5308"`
|
||||
- Update all references to `GlobalResponse<T>` fields from PascalCase to camelCase:
|
||||
- `response.Code` → `response.code`
|
||||
- `response.Message` → `response.message`
|
||||
- `response.Data` → `response.data`
|
||||
- `errorResponse.Code` → `errorResponse.code`
|
||||
- `errorResponse.Message` → `errorResponse.message`
|
||||
- Verify: `ResponseCode` enum values remain unchanged (Ok=10000, etc.)
|
||||
- Verify no other PascalCase field access patterns exist in ApiClient.cs
|
||||
|
||||
> **Rationale for merging**: ApiResponse.cs declares the fields, ApiClient.cs consumes them. Both files must be changed atomically — if committed separately, the codebase is broken regardless of order. A single agent handles both files in one task to guarantee correctness.
|
||||
|
||||
**Must NOT do**:
|
||||
- Do not change `ResponseCode` enum values or names
|
||||
- Do not change `ApiResult<T>` class structure
|
||||
- Do not add `[JsonProperty]` or other attributes
|
||||
- Do not change the `BuildUrl`, `AddAuthHeader`, or `SendAsync` methods
|
||||
- Do not change the method signatures of `GetAsync<T>` or `PostAsync<T>`
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> - **Category**: `quick`
|
||||
> - Reason: Two files, simple field rename + port change, no logic changes
|
||||
> - **Skills**: none required
|
||||
> - **Skills Evaluated but Omitted**: N/A
|
||||
|
||||
**Parallelization**:
|
||||
> - **Can Run In Parallel**: NO (merged from previous Tasks 1+2 due to compilation dependency)
|
||||
> - **Parallel Group**: Wave 1 (with Task 2)
|
||||
> - **Blocks**: None
|
||||
> - **Blocked By**: None (can start immediately)
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/Network/Models/ApiResponse.cs` — Field declaration site
|
||||
- `Assets/Scripts/Online/Network/ApiClient.cs` — Field usage site + BaseUrl
|
||||
- Server `GlobalResponse.cs` at `/mnt/d/Projects/IchniOnline/IchniOnline.Server/Models/Responses/GlobalResponse.cs` — Reference (PascalCase with System.Text.Json camelCase policy)
|
||||
- Backend `launchSettings.json` at `/mnt/d/Projects/IchniOnline/IchniOnline.Server/Properties/launchSettings.json` — Confirms port 5308
|
||||
- Current JSON wire format: `{"code":10000,"message":"Success","data":{...}}` — JSON keys are lowercase
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- `ApiResponse.cs` field declarations use camelCase: `code`, `message`, `data`
|
||||
- `ApiClient.cs` field references use camelCase: `response.code`, `response.message`, `response.data`
|
||||
- `ApiClient.cs` BaseUrl line 20: `"http://localhost:5308"`
|
||||
- `script-execute` confirms `JsonUtility.FromJson<GlobalResponse<string>>` correctly deserializes `{"code":10000,"message":"OK","data":"test"}`
|
||||
|
||||
**QA Scenarios (MANDATORY)**:
|
||||
```
|
||||
Scenario: Verify GlobalResponse deserialization works with camelCase JSON
|
||||
Tool: script-execute (C# Roslyn)
|
||||
Preconditions: Both files have been modified
|
||||
Steps:
|
||||
1. Use script-execute to run: string json = "{\\\"code\\\":10000,\\\"message\\\":\\\"OK\\\",\\\"data\\\":\\\"test\\\"}";
|
||||
2. Deserialize: var obj = UnityEngine.JsonUtility.FromJson<GlobalResponse<string>>(json);
|
||||
3. Assert: obj.code == (ResponseCode)10000
|
||||
4. Assert: obj.message == "OK"
|
||||
5. Assert: obj.data == "test"
|
||||
Expected Result: All 3 fields are correctly populated from camelCase JSON keys
|
||||
Failure Indicators: Any field remains default (0/null)
|
||||
Evidence: .omo/evidence/task-1-globalresponse-deserialize.txt
|
||||
|
||||
Scenario: Verify GlobalResponseBase error deserialization
|
||||
Tool: script-execute
|
||||
Preconditions: Both files have been modified
|
||||
Steps:
|
||||
1. Use script-execute to run: string json = "{\\\"code\\\":10400,\\\"message\\\":\\\"Bad request\\\"}";
|
||||
2. Deserialize as base: var obj = UnityEngine.JsonUtility.FromJson<GlobalResponseBase>(json);
|
||||
3. Assert: obj.code == (ResponseCode)10400
|
||||
4. Assert: obj.message == "Bad request"
|
||||
Expected Result: Error response fields are correctly populated
|
||||
Evidence: .omo/evidence/task-1-globalresponsebase-deserialize.txt
|
||||
|
||||
Scenario: Verify BaseUrl port is corrected
|
||||
Tool: script-read
|
||||
Preconditions: ApiClient.cs has been updated
|
||||
Steps:
|
||||
1. Read line 20 of ApiClient.cs
|
||||
2. Assert: BaseUrl default value is "http://localhost:5308"
|
||||
Expected Result: Port is corrected to 5308
|
||||
Evidence: .omo/evidence/task-1-baseurl.txt
|
||||
|
||||
Scenario: Verify all GlobalResponse field references use camelCase in ApiClient.cs
|
||||
Tool: grep
|
||||
Preconditions: ApiClient.cs has been updated
|
||||
Steps:
|
||||
1. Search for pattern: "response\.Code" in ApiClient.cs — should return 0 matches
|
||||
2. Search for pattern: "response\.code" — should return 2+ matches
|
||||
3. Search for pattern: "errorResponse\.Code" — should return 0 matches
|
||||
4. Search for pattern: "errorResponse\.code" — should return 2+ matches
|
||||
Expected Result: All PascalCase field references replaced with camelCase
|
||||
Evidence: .omo/evidence/task-1-field-access.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] script-execute output for deserialization test
|
||||
- [ ] Line 20 read output
|
||||
- [ ] grep results for field access patterns
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `fix(api): rename GlobalResponse<T> fields to camelCase, correct BaseUrl port to 5308`
|
||||
- Files: `Assets/Scripts/Online/Network/Models/ApiResponse.cs`, `Assets/Scripts/Online/Network/ApiClient.cs`
|
||||
- Pre-commit: Verify build compiles
|
||||
|
||||
---
|
||||
|
||||
- [x] 2. Fix AuthService.cs — third-party login null check
|
||||
|
||||
**What to do**:
|
||||
- In `CompleteTapTapLoginAsync` method (~line 109): Add null check for `result.Data` before accessing `.token`
|
||||
- Handle the case where third-party login succeeds (code=10000) but data is null (unbound account):
|
||||
- If `result.IsSuccess && result.Data != null`: proceed normally (save session, set JWT, fire success)
|
||||
- If `result.IsSuccess && result.Data == null`: fire `OnLoginFailed` with a clear message about account not being bound/linked
|
||||
|
||||
**Must NOT do**:
|
||||
- Do not change the password login flow (LoginWithPasswordAsync)
|
||||
- Do not change register flow
|
||||
- Do not change the encryption logic
|
||||
- Do not modify ThirdPartyServiceManager
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
> - **Category**: `quick`
|
||||
> - Reason: Single null check addition in one method
|
||||
> - **Skills**: none required
|
||||
> - **Skills Evaluated but Omitted**: N/A
|
||||
|
||||
**Parallelization**:
|
||||
> - **Can Run In Parallel**: YES
|
||||
> - **Parallel Group**: Wave 1 (with Tasks 1, 2)
|
||||
> - **Blocks**: None
|
||||
> - **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/Logic/AuthService.cs` — File to edit
|
||||
- Server `AuthController.cs` at `/mnt/d/Projects/IchniOnline/IchniOnline.Server/Controller/AuthController.cs` lines 70-77 — Shows the `ThirdPartyLogin` can return `GlobalResponse<LoginResponse>.Ok(null, "Account not bound")`
|
||||
- `ApiResponse.cs` `ApiResult<T>` class — For understanding `IsSuccess`, `Data` properties
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- `CompleteTapTapLoginAsync` method has null guard before `result.Data.token` access
|
||||
- When `result.IsSuccess && result.Data == null`: calls `OnLoginFailed` with message containing "not bound" or "unbound"
|
||||
- When `result.IsSuccess && result.Data != null`: saves auth session, sets JWT, fires `OnLoginSuccess`
|
||||
- Password login flow (`LoginWithPasswordAsync`) is unchanged
|
||||
|
||||
**QA Scenarios (MANDATORY)**:
|
||||
```
|
||||
Scenario: Verify null data handling for third-party login
|
||||
Tool: script-read
|
||||
Preconditions: AuthService.cs has been updated
|
||||
Steps:
|
||||
1. Read the CompleteTapTapLoginAsync method
|
||||
2. Verify: there is a null check for result.Data before result.Data.token access
|
||||
3. Verify: null case calls OnLoginFailed with appropriate message
|
||||
Expected Result: NullReferenceException is prevented
|
||||
Evidence: .omo/evidence/task-2-null-check.txt
|
||||
|
||||
Scenario: Verify password login flow is unchanged
|
||||
Tool: grep
|
||||
Preconditions: AuthService.cs has been updated
|
||||
Steps:
|
||||
1. Search for "result\.Data\.token" in AuthService.cs — should only appear in the TapTap completion method
|
||||
2. Verify all occurrences have null guards
|
||||
Expected Result: No unprotected access to result.Data.token
|
||||
Evidence: .omo/evidence/task-2-password-flow.txt
|
||||
```
|
||||
|
||||
**Evidence to Capture**:
|
||||
- [ ] Read output showing the null check code
|
||||
- [ ] grep results
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `fix(auth): add null check for third-party login response data`
|
||||
- Files: `Assets/Scripts/Online/Logic/AuthService.cs`
|
||||
- Pre-commit: Verify build compiles
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [ ] F1. **Plan Compliance Audit** — `oracle`
|
||||
Read the plan end-to-end. For each "Must Have": verify implementation exists (read file, check values). For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist in .omo/evidence/. Compare deliverables against plan.
|
||||
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
- [ ] F2. **Scope Fidelity Check** — `deep`
|
||||
For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built (no missing), nothing beyond spec was built (no creep). Check "Must NOT do" compliance. Detect cross-task contamination.
|
||||
Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | VERDICT`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- **1**: `fix(api): rename GlobalResponse<T> fields to camelCase, correct BaseUrl port to 5308`
|
||||
- Files: `ApiResponse.cs`, `ApiClient.cs`
|
||||
- **2**: `fix(auth): add null check for third-party login response data`
|
||||
- Files: `AuthService.cs`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Scenarios
|
||||
1. `script-execute` confirms `JsonUtility.FromJson<GlobalResponse<string>>` correctly parses `{"code":10000,"message":"OK","data":"test"}`
|
||||
2. `ApiClient.cs` line 20 shows `BaseUrl = "http://localhost:5308"`
|
||||
3. `AuthService.cs` has null guard before `result.Data.token` access
|
||||
|
||||
### Final Checklist
|
||||
- [ ] All "Must Have" present
|
||||
- [ ] All "Must NOT Have" absent
|
||||
- [ ] All 3 files modified correctly
|
||||
918
.omo/plans/online-api-integration.md
Normal file
918
.omo/plans/online-api-integration.md
Normal file
@@ -0,0 +1,918 @@
|
||||
# Plan: IchniOnline API Integration
|
||||
|
||||
## TL;DR
|
||||
|
||||
> **Quick Summary**: Create an HTTP API client layer using BestHTTP and implement full auth flows (TapTap third-party login, password login, registration) to connect the Unity game client to the IchniOnline backend server.
|
||||
>
|
||||
> **Deliverables**:
|
||||
> - BestHTTP-based API client with JSON serialization, JWT injection, error handling
|
||||
> - Request/Response DTOs matching server contracts
|
||||
> - Auth orchestration service (TapTap login, password login, register, logout)
|
||||
> - Extended LoginCacheData with JWT + server user data
|
||||
> - Updated LoginPage/StartUIPage to use new auth service
|
||||
>
|
||||
> **Estimated Effort**: Medium
|
||||
> **Parallel Execution**: YES — 4 waves
|
||||
> **Critical Path**: asmdef → ApiClient → AuthService → UI Integration
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Original Request
|
||||
对接 IchniOnline 后端的 API 接口,使用 BestHTTP 进行 HTTP 通信,编写详细计划。所有代码写在 `Scripts/Online` 目录内(IchniOnline.asmdef)。
|
||||
|
||||
### Interview Summary
|
||||
**Key Discussions**:
|
||||
- 服务器开发地址: `localhost:5433`,可配置 Base URL
|
||||
- ThirdParty.Unbound: 暂时跳过,当做登录失败处理
|
||||
- JWT 存储: 扩展 LoginCacheData,加入 JWT + 用户数据,取代纯 TapTap 缓存
|
||||
- 计划范围: TapTap 登录 + 密码登录/注册 + 基础 API 框架
|
||||
|
||||
**Research Findings**:
|
||||
- **BestHTTP** 已安装为 embedded package (`com.tivadar.best.http`),GUID: `9069ac25d95ca17448a247f3bb1c769f`,支持 async/await via `Task<T>`
|
||||
- **TapTap SDK** 登录返回 `TapTapAccount.accessToken`(kid/tokenType/macKey/macAlgorithm),可直接映射到 `ThirdPartyLoginRequest`
|
||||
- **服务端密码加密**: XOR with session key(见 `UserService.DecryptPassword`)
|
||||
- **服务端响应格式**: `GlobalResponse<T> { Code, Message, Data }`,Code 10000=Ok
|
||||
- **JWT 验证**: 服务端使用 JWT Bearer,后续 API 请求需在 Header 注入 `Authorization: Bearer {token}`
|
||||
|
||||
### Metis Review
|
||||
*(Skipped per user request — direct exploration used instead)*
|
||||
|
||||
---
|
||||
|
||||
## Work Objectives
|
||||
|
||||
### Core Objective
|
||||
为 Unity 客户端创建 IchniOnline 后端 API 对接层,实现完整的认证流程(TapTap 第三方登录、密码登录、注册),让客户端和服务端互通。
|
||||
|
||||
### Concrete Deliverables
|
||||
- `Scripts/Online/IchniOnline.asmdef` — 添加 BestHTTP 引用
|
||||
- `Scripts/Online/Network/ApiClient.cs` — BestHTTP 封装单例
|
||||
- `Scripts/Online/Network/Models/ApiResponse.cs` — 响应模型
|
||||
- `Scripts/Online/Network/Models/AuthDtos.cs` — 请求/响应 DTO
|
||||
- `Scripts/Online/Logic/AuthService.cs` — 认证编排服务
|
||||
- `Scripts/Online/Logic/ThirdPartyServiceManager.cs` — 修改:集成 AuthService
|
||||
- `Scripts/Online/Logic/LoginCacheManager.cs` — 修改:扩展 JWT 支持
|
||||
- `Scripts/Online/Models/LoginCacheData.cs` — 修改:添加 JWT 字段
|
||||
|
||||
### Definition of Done
|
||||
- [ ] 编译零错误,无警告
|
||||
- [ ] TapTap 登录后 JWT Token 成功写入本地缓存
|
||||
- [ ] 密码登录流程完整(session key → XOR 加密 → login → JWT)
|
||||
- [ ] 注册流程完整
|
||||
- [ ] LoginPage 使用新 AuthService 驱动流程
|
||||
- [ ] StartUIPage 校验会话有效性
|
||||
|
||||
### Must Have
|
||||
- ApiClient 支持 Base URL 配置(运行时可修改)
|
||||
- ApiClient 自动注入 JWT Bearer 到 Authorization Header
|
||||
- 所有 DTO 映射与服务器 `GlobalResponse<T>` 完全匹配
|
||||
- 密码 XOR 加密算法与服务器端一致
|
||||
- AuthService 对外暴露事件(OnLoginSuccess/OnLoginFailed/etc.)
|
||||
- LoginCacheData 向后兼容(旧缓存不报错)
|
||||
|
||||
### Must NOT Have (Guardrails)
|
||||
- 不要引入新的第三方 HTTP 库(只用 BestHTTP)
|
||||
- 不要修改 `LoginCacheEditor.cs`(但可扩展)
|
||||
- 不要修改 `UI/Base/UIPageBase.cs`
|
||||
- 不要引入 Newtonsoft.Json(用 BestHTTP 内置的 LitJson 或 UnityEngine.JsonUtility)
|
||||
- 不要阻塞主线程(所有 HTTP 请求必须异步)
|
||||
|
||||
---
|
||||
|
||||
## Verification Strategy
|
||||
|
||||
### Test Decision
|
||||
- **Infrastructure exists**: YES (Unity Test Framework)
|
||||
- **Automated tests**: None for network layer (needs running server)
|
||||
- **Agent-Executed QA**: ALWAYS — each task verified via Playwright/browser or manual Unity Editor play mode evidence
|
||||
|
||||
### QA Policy
|
||||
Every task MUST include agent-executed QA scenarios.
|
||||
- C# compilation through Unity Editor domain reload (verify zero compilation errors)
|
||||
- Evidence: Unity Editor console logs, screenshots of play mode, runtime log output
|
||||
|
||||
---
|
||||
|
||||
## Execution Strategy
|
||||
|
||||
### Parallel Execution Waves
|
||||
|
||||
```
|
||||
Wave 1 (Foundation — all parallel):
|
||||
├── Task 1: IchniOnline.asmdef — 添加 BestHTTP 引用 [quick]
|
||||
├── Task 2: ApiResponse.cs — GlobalResponse<T> 响应模型 [quick]
|
||||
├── Task 3: AuthDtos.cs — 请求/响应 DTO [quick]
|
||||
└── Task 4: LoginCacheData.cs — 扩展 JWT 字段 [quick]
|
||||
|
||||
Wave 2 (Core Client — depends on Wave 1):
|
||||
├── Task 5: ApiClient.cs — BestHTTP 封装单例 [unspecified-high]
|
||||
└── Task 6: LoginCacheManager.cs — 扩展 JWT 存取 [quick]
|
||||
Note: Task 5+6 can run in parallel
|
||||
|
||||
Wave 3 (Auth Service — depends on Task 5+6):
|
||||
├── Task 7: AuthService.cs — 认证编排服务 [unspecified-high]
|
||||
Note: Depends on ApiClient + LoginCacheManager
|
||||
|
||||
Wave 4 (UI Integration — depends on Task 7):
|
||||
├── Task 8: ThirdPartyServiceManager.cs — 集成 AuthService [quick]
|
||||
├── Task 9: LoginPage.cs — 使用新 AuthService [visual-engineering]
|
||||
└── Task 10: StartUIPage.cs — 校验会话有效性 [visual-engineering]
|
||||
|
||||
Wave FINAL:
|
||||
├── F1: Plan Compliance Audit (oracle)
|
||||
├── F2: Code Quality Review (unspecified-high)
|
||||
├── F3: Real Manual QA (unspecified-high)
|
||||
└── F4: Scope Fidelity Check (deep)
|
||||
```
|
||||
|
||||
### Dependency Matrix
|
||||
- **Task 1-4**: - → 5, 6 → Wave 2
|
||||
- **Task 5, 6**: 1-4 → 7 → Wave 3
|
||||
- **Task 7**: 5, 6 → 8-10 → Wave 4
|
||||
- **Task 8-10**: 7 → F1-F4 → Final
|
||||
|
||||
---
|
||||
|
||||
## TODOs
|
||||
|
||||
- [x] 1. IchniOnline.asmdef — 添加 BestHTTP 引用
|
||||
|
||||
**What to do**:
|
||||
- 在 `IchniOnline.asmdef` 的 `references` 数组中追加 BestHTTP 的 GUID
|
||||
- BestHTTP Runtime asmdef GUID: `9069ac25d95ca17448a247f3bb1c769f`
|
||||
- 验证 Unity 编译无误,IchniOnline 程序集可以 `using Best.HTTP;`
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要修改其他 asmdef
|
||||
- 不要修改 IchniOnline.Editor.asmdef
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- Reason: 单行 asmdef 修改,极简单的任务
|
||||
- **Skills**: None needed
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 2, 3, 4)
|
||||
- **Blocks**: Tasks 5, 6
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/IchniOnline.asmdef` — 目标文件
|
||||
- `Packages/com.tivadar.best.http/Runtime/com.Tivadar.Best.HTTP.asmdef.meta:L1-7` — GUID: `9069ac25d95ca17448a247f3bb1c769f`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `IchniOnline.asmdef` 的 `references` 数组包含 `"GUID:9069ac25d95ca17448a247f3bb1c769f"`
|
||||
- [ ] Unity 编译完成后,IchniOnline 程序集内可以 `using Best.HTTP;` 无报错
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: asmdef 引用验证
|
||||
Tool: Bash (配合 Unity Editor)
|
||||
Preconditions: Unity Editor 已打开,IchniOnline.asmdef 已修改
|
||||
Steps:
|
||||
1. 在 Unity 中等待编译完成(AssetDatabase.Refresh 或观察 Console 无红错)
|
||||
2. 打开 `Assets/Scripts/Online/IchniOnline.asmdef` 确认 references 包含 BestHTTP GUID
|
||||
Expected Result: 编译零错误,无 warning
|
||||
Evidence: .omo/evidence/task-1-asmdef-refs.png (Editor 无错误截图)
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 2-4)
|
||||
- Message: `feat(online): add BestHTTP reference to IchniOnline.asmdef`
|
||||
- Files: `Assets/Scripts/Online/IchniOnline.asmdef`
|
||||
- Pre-commit: 验证 Unity 编译通过
|
||||
|
||||
---
|
||||
|
||||
- [x] 2. ApiResponse.cs — 创建服务端响应模型
|
||||
|
||||
**What to do**:
|
||||
- 新建文件: `Assets/Scripts/Online/Network/Models/ApiResponse.cs`
|
||||
- 定义 `ResponseCode` enum(匹配服务端 `Models/Responses/ResponseCode.cs`)
|
||||
- 定义 `GlobalResponse<T>` class(匹配服务端 `GlobalResponse<T>`)
|
||||
- 定义 `ApiResult<T>` 封装类,包含成功/失败状态 + 错误信息
|
||||
- 使用 `System.Text.Json` 或 `UnityEngine.JsonUtility` 做序列化(优先 JsonUtility 避免额外依赖)
|
||||
- 命名空间: `IchniOnline.Online.Network.Models`
|
||||
|
||||
**关键映射**:
|
||||
```csharp
|
||||
// 服务端 ResponseCode
|
||||
public enum ResponseCode {
|
||||
Ok = 10000,
|
||||
BadRequest = 10400,
|
||||
Unauthorized = 10401,
|
||||
Forbidden = 10403,
|
||||
NotFound = 10404,
|
||||
InternalServerError = 10500
|
||||
}
|
||||
|
||||
// 服务端 GlobalResponse<T>
|
||||
// 注意: Unity JsonUtility 不支持泛型直接反序列化,需要包装或使用封装层
|
||||
// 方案: 实现一个非泛型的 ApiResponse 做中间反序列化,再通过 ApiResult<T> 取 Data
|
||||
```
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要引入 Newtonsoft.Json(项目中已无引用)
|
||||
- 不要将序列化逻辑写死到 ApiClient 之外
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- Reason: 纯数据模型定义,无复杂逻辑
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 1, 3, 4)
|
||||
- **Blocks**: Tasks 5, 6
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\GlobalResponse.cs` — 服务端 GlobalResponse 实现
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\ResponseCode.cs` — 服务端 ResponseCode enum
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `ApiResponse.cs` 中定义 `ResponseCode` enum(Ok/BadRequest/Unauthorized/Forbidden/NotFound/InternalServerError)
|
||||
- [ ] `GlobalResponse<T>` class 包含 Code/Message/Data 三个字段
|
||||
- [ ] Unity 编译通过,无错误
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 编译验证
|
||||
Tool: Unity Editor
|
||||
Preconditions: 文件创建完成,Unity 编译通过
|
||||
Steps:
|
||||
1. 在任意脚本中添加 `using IchniOnline.Online.Network.Models;`
|
||||
2. 使用 `ResponseCode.Ok` 确认 enum 可用
|
||||
Expected Result: 编译零错误
|
||||
Evidence: .omo/evidence/task-2-compile.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 1, 3, 4)
|
||||
|
||||
---
|
||||
|
||||
- [x] 3. AuthDtos.cs — 创建认证请求/响应 DTO
|
||||
|
||||
**What to do**:
|
||||
- 新建文件: `Assets/Scripts/Online/Network/Models/AuthDtos.cs`
|
||||
- 定义以下 DTO(匹配服务端 Models):
|
||||
|
||||
```csharp
|
||||
// 请求 DTO
|
||||
[System.Serializable]
|
||||
public class ThirdPartyLoginRequestDto {
|
||||
public string Token; // accessToken.kid
|
||||
public string TokenType; // accessToken.tokenType ("mac")
|
||||
public string MacKey; // accessToken.macKey
|
||||
public string MacAlgorithm; // accessToken.macAlgorithm ("hmac-sha-1")
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class LoginRequestDto {
|
||||
public string Username;
|
||||
public string EncryptedPassword; // Base64 of XOR'd bytes
|
||||
public string SessionKey;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class RegisterRequestDto {
|
||||
public string Username;
|
||||
public string Password;
|
||||
public string DisplayName;
|
||||
}
|
||||
|
||||
// 响应 DTO(与服务端 AuthResponse.cs 对应)
|
||||
[System.Serializable]
|
||||
public class SessionKeyResponseDto {
|
||||
public string sessionKey;
|
||||
public string expiresAt;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class LoginResponseDto {
|
||||
public string Token; // JWT
|
||||
public UserResponseDto User;
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class UserResponseDto {
|
||||
public string UserId;
|
||||
public string Username;
|
||||
public string DisplayName;
|
||||
public string AvatarUrl;
|
||||
public int Permission; // 0=Guest, 1=Player, 2=Admin
|
||||
}
|
||||
```
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要包含非认证相关的 DTO(如 Beatmap 相关)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- Reason: 纯数据结构定义
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 1, 2, 4)
|
||||
- **Blocks**: Tasks 5, 7
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Requests\ThirdPartyLoginRequest.cs`
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Requests\LoginRequest.cs`
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Requests\RegisterRequest.cs`
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\AuthResponse.cs`
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] 所有 DTO 定义为 `[System.Serializable]`
|
||||
- [ ] Unity 编译通过
|
||||
- [ ] 字段名小写(JSON 反序列化兼容服务端 PascalCase → JsonUtility 需字段名匹配)
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 编译验证
|
||||
Tool: Unity Editor
|
||||
Preconditions: 文件创建完成
|
||||
Steps: Unity 编译自动触发
|
||||
Expected Result: 无编译错误
|
||||
Evidence: .omo/evidence/task-3-compile.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 1, 2, 4)
|
||||
|
||||
---
|
||||
|
||||
- [x] 4. LoginCacheData.cs — 扩展 JWT 和服务器用户数据字段
|
||||
|
||||
**What to do**:
|
||||
- 编辑 `Assets/Scripts/Online/Models/LoginCacheData.cs`
|
||||
- 添加以下字段:
|
||||
```csharp
|
||||
public string jwtToken; // JWT Bearer token
|
||||
public string userId; // 服务端返回的 UserId (Guid 字符串)
|
||||
public string displayName; // 服务端返回的 DisplayName
|
||||
public string avatarUrl; // 服务端返回的 AvatarUrl
|
||||
public int permission; // 服务端返回的 Permission
|
||||
public bool hasServerSession; // 标记是否已完成服务端认证
|
||||
```
|
||||
- 更新 `IsValid` 逻辑:`hasServerSession && !string.IsNullOrEmpty(jwtToken)`
|
||||
- 保持向后兼容:构造旧字段保留,无 session 时 `hasServerSession=false`
|
||||
- 添加 `UpdateFromServerResponse(LoginResponseDto response)` 方法
|
||||
- 添加 `ClearServerSession()` 方法(仅清除 JWT 系列字段,保留 TapTap 原始信息)
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要删除已有字段 (openId/unionId/name/avatar/email/cacheTimestamp)
|
||||
- 不要破坏 `LoginCacheEditor.cs` 中使用的公开接口
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- Reason: 小幅度字段扩展,结构简单
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES
|
||||
- **Parallel Group**: Wave 1 (with Tasks 1, 2, 3)
|
||||
- **Blocks**: Task 6
|
||||
- **Blocked By**: None
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/Models/LoginCacheData.cs` — 当前文件
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\AuthResponse.cs` — 服务端 LoginResponse/UserResponse
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `LoginCacheData` 包含新字段 `jwtToken`, `userId`, `displayName`, `avatarUrl`, `permission`, `hasServerSession`
|
||||
- [ ] 旧 ES3 缓存数据加载后不会报错(缺失字段为默认值,`hasServerSession=false`)
|
||||
- [ ] `IsValid` 在 `hasServerSession` 为 true 时检查 `jwtToken` 非空
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 向后兼容测试
|
||||
Tool: Unity Editor + Bash (ES3 操作)
|
||||
Preconditions: LoginCacheData 已修改,Unity 已编译
|
||||
Steps:
|
||||
1. 用 LoginCacheEditor.GenerateMockData 写入旧格式数据
|
||||
2. 读取 LoginCacheManager.CachedData
|
||||
3. 确认 hasServerSession=false,旧字段值正确
|
||||
Expected Result: 旧缓存被正确加载,不丢失数据,不报错
|
||||
Evidence: .omo/evidence/task-4-backward-compat.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 1, 2, 3)
|
||||
|
||||
---
|
||||
|
||||
- [x] 5. ApiClient.cs — BestHTTP 封装单例
|
||||
|
||||
**What to do**:
|
||||
- 新建文件: `Assets/Scripts/Online/Network/ApiClient.cs`
|
||||
- 创建 `IchniOnlineApiClient` 类(非 MonoBehaviour 单例,或挂载到 DontDestroyOnLoad 对象)
|
||||
- 核心功能:
|
||||
1. **Base URL 配置**: `public string BaseUrl { get; set; }`,初始值 `http://localhost:5433`
|
||||
2. **JWT 管理**: `public string JwtToken { get; set; }`,设置后自动在请求头注入
|
||||
3. **GET 请求**: `Task<ApiResult<T>> GetAsync<T>(string endpoint)`
|
||||
4. **POST 请求**: `Task<ApiResult<T>> PostAsync<T>(string endpoint, object body)`
|
||||
5. **内部实现**: 使用 BestHTTP 的 `HTTPRequest` + `GetHTTPResponseAsync()`
|
||||
- POST body 序列化: `request.RawData = Encoding.UTF8.GetBytes(JsonUtility.ToJson(body))`
|
||||
- Header 设置: `request.SetHeader("Content-Type", "application/json")`
|
||||
- JWT 注入: `request.SetHeader("Authorization", $"Bearer {JwtToken}")`
|
||||
6. **响应解析**: 从 `resp.DataAsText` 反序列化为 `GlobalResponse<T>`,提取 Data
|
||||
7. **错误处理**:
|
||||
- HTTP 状态码错误 → ApiResult 含错误信息
|
||||
- 解析失败 → ApiResult 含异常信息
|
||||
- 网络超时 → ApiResult.Timeout
|
||||
- 服务器返回失败码 → 按 Code 分类处理
|
||||
|
||||
**Error Handling Design**:
|
||||
```csharp
|
||||
public class ApiResult<T> {
|
||||
public bool IsSuccess;
|
||||
public T Data;
|
||||
public int Code; // ResponseCode
|
||||
public string Message; // 服务端返回的 Message
|
||||
public string ErrorDetail; // 客户端错误详情
|
||||
|
||||
public static ApiResult<T> Ok(T data) => ...;
|
||||
public static ApiResult<T> Fail(int code, string msg, string detail = null) => ...;
|
||||
}
|
||||
```
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要阻塞 Unity 主线程(所有请求使用 async/await)
|
||||
- 不要硬编码 URL(BaseUrl 必须可配置)
|
||||
- 不要在 ApiClient 内处理业务逻辑(仅做 HTTP 通信)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `unspecified-high`
|
||||
- Reason: 需要理解 BestHTTP API、异步模式、JSON 序列化,有较多细节
|
||||
- **Skills**: None (BestHTTP API 文档已通过前期研究覆盖)
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Task 6)
|
||||
- **Parallel Group**: Wave 2 (with Task 6)
|
||||
- **Blocks**: Task 7
|
||||
- **Blocked By**: Tasks 1, 2, 3
|
||||
|
||||
**References**:
|
||||
- `Packages/com.tivadar.best.http/Runtime/HTTP/HTTPRequestAsyncExtensions.cs` — BestHTTP async extension methods
|
||||
- `Packages/com.tivadar.best.http/Runtime/HTTP/HTTPRequest.cs` — HTTPRequest class API
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Models\Responses\GlobalResponse.cs` — 服务端响应格式
|
||||
- `Assets/Scripts/Online/Network/Models/ApiResponse.cs` — 客户端响应模型(同 Wave 1 Task 2)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `IchniOnlineApiClient` 可配置 `BaseUrl`
|
||||
- [ ] `GetAsync<T>` 发送 GET 请求并正确解析 `GlobalResponse<T>`
|
||||
- [ ] `PostAsync<T>` 发送 POST 请求,body 序列化为 JSON
|
||||
- [ ] JWT Token 自动注入到 Authorization Header
|
||||
- [ ] 网络错误/服务端错误被正确封装为 `ApiResult<T>`
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: GET 请求 + 响应解析
|
||||
Tool: Unity Editor + Bash (可启动测试后端或 mock)
|
||||
Preconditions: IchniOnline 后端在 localhost:5433 运行(或 mock 端点)
|
||||
Steps:
|
||||
1. 在 Unity Start() 中调用 ApiClient.GetAsync<object>("/api/test/health")
|
||||
2. 检查 Console 输出请求/响应日志
|
||||
Expected Result: 请求发送成功,响应被正确解析
|
||||
Evidence: .omo/evidence/task-5-get-request.png
|
||||
|
||||
Scenario: JWT 注入验证
|
||||
Tool: Unity Editor
|
||||
Preconditions: ApiClient.JwtToken 设置为 "test-token"
|
||||
Steps:
|
||||
1. 发起 PostAsync 请求
|
||||
2. 检查请求 Header 包含 "Authorization: Bearer test-token"
|
||||
Expected Result: Authorization Header 正确注入
|
||||
Evidence: .omo/evidence/task-5-jwt-header.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Task 6)
|
||||
- Message: `feat(online): implement API client and extend login cache`
|
||||
|
||||
---
|
||||
|
||||
- [x] 6. LoginCacheManager.cs — 扩展 JWT 存取方法
|
||||
|
||||
**What to do**:
|
||||
- 编辑 `Assets/Scripts/Online/Logic/LoginCacheManager.cs`
|
||||
- 添加新方法:
|
||||
```csharp
|
||||
// 保存完整认证会话(JWT + 用户数据)
|
||||
public static void SaveAuthSession(string jwtToken, LoginResponseDto response)
|
||||
|
||||
// 清除会话(保留 TapTap 原始数据,清除 JWT/服务端数据)
|
||||
public static void ClearSession()
|
||||
|
||||
// 获取缓存的 JWT Token
|
||||
public static string CachedJwtToken { get; }
|
||||
|
||||
// 检查是否有有效的服务端会话
|
||||
public static bool HasValidSession { get; }
|
||||
```
|
||||
- ES3 key 保持不变: `Ichni_LoginCache`
|
||||
- `HasCachedLogin` → 改为检查 `HasValidSession`
|
||||
- `CachedData` → 保持返回完整数据
|
||||
- `SaveFromTapTapAccount` → 保持原有行为(只存 TapTap 数据,不清除已有 JWT)
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要修改 ES3 key 名称
|
||||
- 不要改变已有方法的签名
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- Reason: 简单的方法扩展
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Task 5)
|
||||
- **Parallel Group**: Wave 2 (with Task 5)
|
||||
- **Blocks**: Task 7
|
||||
- **Blocked By**: Tasks 1, 4
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/Logic/LoginCacheManager.cs` — 当前文件
|
||||
- `Assets/Scripts/Online/Models/LoginCacheData.cs` — 扩展后的数据模型
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `SaveAuthSession(jwt, response)` 正确写入 ES3
|
||||
- [ ] `CachedJwtToken` 从 ES3 读取正确
|
||||
- [ ] `HasValidSession` 在 jwt 为空或无 session 时返回 false
|
||||
- [ ] 编辑器中 `Ichni/Login Cache` 菜单仍正常工作
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 保存/读取 JWT 会话
|
||||
Tool: Unity Editor
|
||||
Preconditions: 已有 LoginCacheData 模型扩展
|
||||
Steps:
|
||||
1. LoginCacheManager.SaveAuthSession("test-jwt", mockLoginResponse)
|
||||
2. 读取 LoginCacheManager.CachedJwtToken
|
||||
3. 读取 LoginCacheManager.HasValidSession
|
||||
Expected Result: jwt="test-jwt", HasValidSession=true
|
||||
Evidence: .omo/evidence/task-6-save-jwt.png
|
||||
|
||||
Scenario: 清除会话
|
||||
Steps:
|
||||
1. LoginCacheManager.ClearSession()
|
||||
2. 检查 HasValidSession
|
||||
Expected Result: HasValidSession=false, TapTap 原始数据保留
|
||||
Evidence: .omo/evidence/task-6-clear.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Task 5)
|
||||
|
||||
---
|
||||
|
||||
- [x] 7. AuthService.cs — 认证编排服务
|
||||
|
||||
**What to do**:
|
||||
- 新建文件: `Assets/Scripts/Online/Logic/AuthService.cs`
|
||||
- 创建 `IchniOnlineAuthService` 类(非 MonoBehaviour 静态类 或 通过 ThirdPartyServiceManager 实例管理)
|
||||
- 核心接口:
|
||||
|
||||
```csharp
|
||||
public static class IchniOnlineAuthService {
|
||||
// 事件
|
||||
public static event Action<LoginResponseDto> OnLoginSuccess;
|
||||
public static event Action<string> OnLoginFailed; // error message
|
||||
public static event Action OnLoginCanceled;
|
||||
|
||||
// 属性
|
||||
public static bool IsLoggingIn { get; }
|
||||
public static bool IsLoggedIn { get; } // HasValidSession
|
||||
|
||||
// TapTap 第三方登录流程
|
||||
// 1. 调用 ThirdPartyServiceManager.StartTapTapLogin()
|
||||
// 2. 在 OnLoginSuccess 回调中:
|
||||
// a. 从 TapTapAccount 取出 accessToken
|
||||
// b. 构造 ThirdPartyLoginRequestDto
|
||||
// c. 调用 ApiClient.PostAsync<LoginResponseDto>("/api/auth/third-party/login", body)
|
||||
// d. 成功 → LoginCacheManager.SaveAuthSession(jwt, response)
|
||||
// e. 失败 → 抛 OnLoginFailed
|
||||
// f. ThirdParty.Unbound → 抛 OnLoginFailed("TapTap account not bound")
|
||||
public static void LoginWithTapTap();
|
||||
|
||||
// 密码登录流程
|
||||
// 1. GET /api/auth/session-key → 得到 sessionKey
|
||||
// 2. 密码 XOR 加密:
|
||||
// byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
|
||||
// byte[] sessionBytes = Encoding.UTF8.GetBytes(sessionKey);
|
||||
// byte[] encrypted = new byte[passwordBytes.Length];
|
||||
// for (int i = 0; i < passwordBytes.Length; i++)
|
||||
// encrypted[i] = (byte)(passwordBytes[i] ^ sessionBytes[i % sessionBytes.Length]);
|
||||
// string encryptedPassword = Convert.ToBase64String(encrypted);
|
||||
// 3. POST /api/auth/login with { Username, EncryptedPassword, SessionKey }
|
||||
// 4. 成功 → LoginCacheManager.SaveAuthSession(jwt, response)
|
||||
public static async void LoginWithPassword(string username, string password);
|
||||
|
||||
// 注册流程
|
||||
// POST /api/auth/register with { Username, Password, DisplayName }
|
||||
// 注意: 注册后不自动返回 JWT,需要用户手动登录
|
||||
public static async void Register(string username, string password, string displayName);
|
||||
|
||||
// 登出
|
||||
public static void Logout();
|
||||
|
||||
// 密码 XOR 加密工具方法(可复用)
|
||||
public static string EncryptPassword(string password, string sessionKey);
|
||||
}
|
||||
```
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要直接调用 TapTap SDK 的 LoginWithScopes(由 ThirdPartyServiceManager 封装)
|
||||
- 不要在 AuthService 中管理 UI 状态(只触发事件,由 LoginPage 处理 UI)
|
||||
- 不要硬编码 API 路径(用字符串常量集中管理)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `unspecified-high`
|
||||
- Reason: 涉及多步异步流程编排、事件管理、加密算法实现
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: NO
|
||||
- **Parallel Group**: Wave 3 (sequential)
|
||||
- **Blocks**: Tasks 8, 9, 10
|
||||
- **Blocked By**: Tasks 5, 6
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/Logic/ThirdPartyServiceManager.cs` — TapTap SDK 调用封装
|
||||
- `Assets/Scripts/Online/Network/ApiClient.cs` — HTTP 通信(Task 5)
|
||||
- `Assets/Scripts/Online/Logic/LoginCacheManager.cs` — 缓存管理(Task 6)
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Service\UserService.cs:213-225` — 服务端 XOR 解密算法(客户端需反转实现加密)
|
||||
- `D:\Projects\IchniOnline\IchniOnline.Server\Controller\AuthController.cs` — 服务端 API 端点定义
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `LoginWithTapTap()` 完整流程: TapTap SDK → API call → JWT → 事件
|
||||
- [ ] `LoginWithPassword()` 完整流程: session key → XOR 加密 → API call → JWT → 事件
|
||||
- [ ] `Register()` 调用 POST /api/auth/register
|
||||
- [ ] `Logout()` 清除 JWT + TapTap 登出
|
||||
- [ ] `EncryptPassword()` 与服务端 `DecryptPassword` 互为逆运算
|
||||
- [ ] 所有事件正确触发(OnLoginSuccess/OnLoginFailed/OnLoginCanceled)
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 密码 XOR 加密验证
|
||||
Tool: C# 脚本验证
|
||||
Preconditions: AuthService.EncryptPassword 已实现
|
||||
Steps:
|
||||
1. 设 password="test123", sessionKey="base64encodedkey=="
|
||||
2. 调用 EncryptPassword 得到 encrypted
|
||||
3. 用服务端 DecryptPassword 逻辑解密: 取 encrypted Base64 → XOR with sessionKey bytes → 得到明文
|
||||
Expected Result: 解密后明文等于原始 password
|
||||
Evidence: .omo/evidence/task-7-xor-verify.png
|
||||
|
||||
Scenario: TapTap 登录流程(模拟)
|
||||
Tool: Unity Editor Play Mode
|
||||
Preconditions: ThirdPartyServiceManager 已初始化
|
||||
Steps:
|
||||
1. AuthService.LoginWithTapTap()
|
||||
2. Console 观察: TapTap SDK 登录 → API POST 请求 → JWT 缓存
|
||||
Expected Result: 流程完整无异常
|
||||
Evidence: .omo/evidence/task-7-taaptap-flow.png
|
||||
```
|
||||
|
||||
**Commit**: YES
|
||||
- Message: `feat(online): implement auth service with TapTap/password/register flows`
|
||||
- Files: `Assets/Scripts/Online/Logic/AuthService.cs`
|
||||
- Pre-commit: Unity 编译检查
|
||||
|
||||
---
|
||||
|
||||
- [x] 8. ThirdPartyServiceManager.cs — 集成 AuthService
|
||||
|
||||
**What to do**:
|
||||
- 编辑 `Assets/Scripts/Online/Logic/ThirdPartyServiceManager.cs`
|
||||
- 在 `StartTapTapLogin()` 的 `LoginWithScopes` 成功后,**不再直接调用** `LoginCacheManager.SaveFromTapTapAccount(account)`(该操作移至 AuthService 流程中)
|
||||
- 添加事件 `OnTapTapAccessTokenReceived(AccessToken token)` 或修改流程,使 AuthService 能拿到 accessToken
|
||||
- 可选方案 A: ThirdPartyServiceManager 保留纯 SDK 封装,AuthService 通过 `TapTapLogin.Instance.GetCurrentTapAccount()` 获取 account
|
||||
- 可选方案 B: ThirdPartyServiceManager 的 `OnLoginSuccess` 事件中附带 accessToken
|
||||
- **推荐方案 B**: 扩展 `OnLoginSuccess` 事件参数或添加新事件
|
||||
```csharp
|
||||
// 新增事件: 携带 accessToken(供 AuthService 使用)
|
||||
public event Action<TapTapAccount, AccessToken> OnLoginWithToken;
|
||||
```
|
||||
- `StartTapTapLogin()` 保持不变(依然触发 OnLoginSuccess/OnLoginCanceled/OnLoginFailed)
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要移除已有的事件(OnLoginSuccess/OnLoginCanceled/OnLoginFailed)
|
||||
- 不要改变 TapTap SDK 初始化逻辑
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `quick`
|
||||
- Reason: 小幅接口扩展
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 9, 10)
|
||||
- **Parallel Group**: Wave 4 (with Tasks 9, 10)
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: Task 7
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/Online/Logic/ThirdPartyServiceManager.cs` — 当前文件
|
||||
- `D:\Projects\Open\TapSDKLogin-Unity\Runtime\Public\TapTapAccount.cs` — TapTapAccount 结构(含 accessToken 属性)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `OnLoginWithToken` 事件在 TapTap 登录成功后触发
|
||||
- [ ] 事件参数包含完整的 `TapTapAccount` 和 `AccessToken`
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 事件触发测试
|
||||
Tool: Unity Editor Play Mode
|
||||
Preconditions: ThirdPartyServiceManager 已初始化
|
||||
Steps:
|
||||
1. 订阅 ThirdPartyServiceManager.Instance.OnLoginWithToken
|
||||
2. 调用 StartTapTapLogin()
|
||||
3. TapTap 登录成功后检查事件是否触发
|
||||
Expected Result: 事件触发,accessToken.kid/macKey/macAlgorithm 非空
|
||||
Evidence: .omo/evidence/task-8-event.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 9, 10)
|
||||
- Message: `feat(ui): integrate auth service into login UI`
|
||||
|
||||
---
|
||||
|
||||
- [x] 9. LoginPage.cs — 使用新 AuthService 驱动流程
|
||||
|
||||
**What to do**:
|
||||
- 编辑 `Assets/Scripts/UI/LoginPage/LoginPage.cs`
|
||||
- 修改 `OnTapTapClicked()`:
|
||||
- 不再直接调用 `ThirdPartyServiceManager.Instance.StartTapTapLogin()`
|
||||
- 改为调用 `IchniOnlineAuthService.LoginWithTapTap()`
|
||||
- 修改 `OnTapTapLoginSuccess(TapTapAccount account)`:
|
||||
- 不再立即 FadeOut(改为等待 AuthService 的服务端验证完成)
|
||||
- 改为 `IchniOnlineAuthService.OnLoginSuccess += OnApiLoginSuccess`
|
||||
- 添加新回调 `OnApiLoginSuccess(LoginResponseDto response)`:
|
||||
- 服务端验证成功后 FadeOut + 恢复 StartPage
|
||||
- 添加 UI 加载状态:
|
||||
- 显示加载指示器(或按钮禁用+文字变化)在等待服务端响应期间
|
||||
- 事件订阅:
|
||||
- 订阅 `AuthService.OnLoginSuccess` / `OnLoginFailed` / `OnLoginCanceled`
|
||||
- 替代原有的 ThirdPartyServiceManager 事件(或共存)
|
||||
- 账户密码登录 UI:
|
||||
- 如果 LoginPage 上已有用户名/密码输入框 → 绑定到 `IchniOnlineAuthService.LoginWithPassword()`
|
||||
- 如果没有 → 增加简单的用户名/密码输入框 + 登录按钮
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要删除 closeButton 和原有的 UI 结构
|
||||
- 不要修改 UIPageBase 基类
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- Reason: 涉及 UI 逻辑更新和用户交互流程
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 8, 10)
|
||||
- **Parallel Group**: Wave 4 (with Tasks 8, 10)
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: Task 7
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/UI/LoginPage/LoginPage.cs` — 当前文件
|
||||
- `Assets/Scripts/Online/Logic/AuthService.cs` — 认证服务(Task 7)
|
||||
- `Assets/Scripts/UI/StartPage/StartUIPage.cs` — StartPage 交互恢复
|
||||
- `Assets/Scripts/UI/StartPage/StartUIPage.cs:TouchToStart()` — 缓存检测入口
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] TapTap 按钮触发 `AuthService.LoginWithTapTap()` 而非直接调用 ThirdPartyServiceManager
|
||||
- [ ] 登录请求加载状态有 UI 反馈
|
||||
- [ ] 服务端返回成功后 FadeOut + 恢复 StartPage
|
||||
- [ ] 失败时恢复按钮交互
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: TapTap 登录 UI 流程
|
||||
Tool: Unity Editor Play Mode
|
||||
Preconditions: LoginPage 在 MenuScene 中打开
|
||||
Steps:
|
||||
1. 点击 TapTap 按钮
|
||||
2. 观察按钮变为非交互状态
|
||||
3. TapTap SDK 登录窗口出现
|
||||
4. 完成 TapTap 登录 → 等待服务端验证
|
||||
5. 成功后 LoginPage 淡出 → 恢复 StartPage
|
||||
Expected Result: UI 流程完整,无中断
|
||||
Evidence: .omo/evidence/task-9-ui-flow.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 8, 10)
|
||||
|
||||
---
|
||||
|
||||
- [x] 10. StartUIPage.cs — 校验会话有效性
|
||||
|
||||
**What to do**:
|
||||
- 编辑 `Assets/Scripts/UI/StartPage/StartUIPage.cs`
|
||||
- `TouchToStart()` 中改用 `LoginCacheManager.HasValidSession` 替代 `LoginCacheManager.HasCachedLogin`
|
||||
- 添加可选的 Token 过期检测:
|
||||
- JWT 本身包含过期时间(可解析 payload 中的 exp 字段)
|
||||
- 如果 JWT 已过期 → 清除 session → 显示 LoginPage
|
||||
- 简单方案: 暂不做 JWT 解码解析,只检查 `HasValidSession`
|
||||
- 如有 `RestoreInteraction()` 方法保持现有逻辑
|
||||
|
||||
**Must NOT do**:
|
||||
- 不要修改不相关的 StartUIPage 逻辑
|
||||
- 不要引入 JWT 解码库(暂不解码 JWT payload)
|
||||
|
||||
**Recommended Agent Profile**:
|
||||
- **Category**: `visual-engineering`
|
||||
- **Skills**: None
|
||||
|
||||
**Parallelization**:
|
||||
- **Can Run In Parallel**: YES (with Tasks 8, 9)
|
||||
- **Parallel Group**: Wave 4 (with Tasks 8, 9)
|
||||
- **Blocks**: None
|
||||
- **Blocked By**: Task 7
|
||||
|
||||
**References**:
|
||||
- `Assets/Scripts/UI/StartPage/StartUIPage.cs` — 当前文件
|
||||
- `Assets/Scripts/Online/Logic/LoginCacheManager.cs` — 扩展后的缓存管理器(Task 6)
|
||||
|
||||
**Acceptance Criteria**:
|
||||
- [ ] `TouchToStart()` 使用 `LoginCacheManager.HasValidSession` 判断
|
||||
- [ ] 有有效 session → 直接进 ChapterSelection
|
||||
- [ ] 无有效 session → 显示 LoginPage
|
||||
- [ ] Unity 编译通过
|
||||
|
||||
**QA Scenarios**:
|
||||
```
|
||||
Scenario: 有 JWT session 时跳过登录
|
||||
Tool: Unity Editor Play Mode
|
||||
Preconditions: ES3 中存在有效 JWT 缓存
|
||||
Steps:
|
||||
1. 进入 MenuScene
|
||||
2. 点击 TouchToStart
|
||||
Expected Result: 直接进入 ChapterSelection,不显示 LoginPage
|
||||
Evidence: .omo/evidence/task-10-skip-login.png
|
||||
|
||||
Scenario: 无 JWT session 时显示登录
|
||||
Preconditions: 清除 ES3 缓存
|
||||
Steps:
|
||||
1. 进入 MenuScene
|
||||
2. 点击 TouchToStart
|
||||
Expected Result: LoginPage 显示
|
||||
Evidence: .omo/evidence/task-10-show-login.png
|
||||
```
|
||||
|
||||
**Commit**: YES (group with Tasks 8, 9)
|
||||
|
||||
---
|
||||
|
||||
## Final Verification Wave
|
||||
|
||||
- [x] F1. **Plan Compliance Audit** — `oracle`
|
||||
Read the plan end-to-end. For each "Must Have": verify implementation exists (read file, check compilation). For each "Must NOT Have": search codebase for forbidden patterns — reject with file:line if found. Check evidence files exist in .omo/evidence/. Compare deliverables against plan.
|
||||
Output: `Must Have [N/N] | Must NOT Have [N/N] | Tasks [N/N] | VERDICT: APPROVE/REJECT`
|
||||
|
||||
- [x] F2. **Code Quality Review** — `unspecified-high`
|
||||
Check compilation status. Review changed files: proper async patterns (no sync-over-async), no `try/catch` swallowing, proper error handling, no magic strings (use constants), correct `[Serializable]` attributes on DTOs.
|
||||
Output: `Build [PASS/FAIL] | Code Quality [N clean/N issues] | VERDICT`
|
||||
|
||||
- [x] F3. **Real Manual QA** — `unspecified-high`
|
||||
Start from clean scene state. Execute QA scenarios from EVERY task — follow exact steps, capture evidence. Test integration flow: TapTap login → API call → JWT cache → restart → skip login. Test edge cases: network error, invalid credentials, ThirdParty.Unbound.
|
||||
Output: `Scenarios [N/N pass] | Integration [N/N] | Edge Cases [N tested] | VERDICT`
|
||||
|
||||
- [x] F4. **Scope Fidelity Check** — `deep`
|
||||
For each task: read "What to do", read actual diff (git log/diff). Verify 1:1 — everything in spec was built (no missing), nothing beyond spec was built (no creep). Check "Must NOT do" compliance. Detect cross-task contamination.
|
||||
Output: `Tasks [N/N compliant] | Contamination [CLEAN/N issues] | Unaccounted [CLEAN/N files] | VERDICT`
|
||||
|
||||
---
|
||||
|
||||
## Commit Strategy
|
||||
|
||||
- **C1** (Task 1-4): `feat(online): add network models and asmdef references`
|
||||
- **C2** (Task 5-6): `feat(online): implement API client and extend login cache`
|
||||
- **C3** (Task 7): `feat(online): implement auth service with TapTap/password/register flows`
|
||||
- **C4** (Task 8-10): `feat(ui): integrate auth service into login UI`
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria
|
||||
|
||||
### Verification Commands
|
||||
```bash
|
||||
# In Unity Editor:
|
||||
# 1. Open MenuScene
|
||||
# 2. Enter Play Mode
|
||||
# 3. Click TapTap Login button → should call POST /api/auth/third-party/login
|
||||
# 4. Check Console for network request/response logs
|
||||
# 5. Exit Play Mode → check ES3 cache contains JWT data
|
||||
```
|
||||
|
||||
### Final Checklist
|
||||
- [ ] No compilation errors in IchniOnline.asmdef or dependent assemblies
|
||||
- [ ] TapTap login → server API call → JWT → ES3 cache
|
||||
- [ ] Password login (if server running) → JWT → ES3 cache
|
||||
- [ ] Register (if server running) → user created
|
||||
- [ ] LoginPage responds to all auth states (loading/success/failure)
|
||||
10
.omo/run-continuation/ses_12b41534affeQDNAisTmCG0XrZ.json
Normal file
10
.omo/run-continuation/ses_12b41534affeQDNAisTmCG0XrZ.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_12b41534affeQDNAisTmCG0XrZ",
|
||||
"updatedAt": "2026-06-17T08:45:47.266Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-06-17T08:45:47.266Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
.omo/run-continuation/ses_12b516164ffekTIOv7bJsDWp4s.json
Normal file
10
.omo/run-continuation/ses_12b516164ffekTIOv7bJsDWp4s.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_12b516164ffekTIOv7bJsDWp4s",
|
||||
"updatedAt": "2026-06-17T08:25:45.254Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-06-17T08:25:45.254Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
10
.omo/run-continuation/ses_135a9996effen0Yyr1bZVA5svu.json
Normal file
10
.omo/run-continuation/ses_135a9996effen0Yyr1bZVA5svu.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"sessionID": "ses_135a9996effen0Yyr1bZVA5svu",
|
||||
"updatedAt": "2026-06-15T08:17:13.685Z",
|
||||
"sources": {
|
||||
"background-task": {
|
||||
"state": "idle",
|
||||
"updatedAt": "2026-06-15T08:17:13.685Z"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,7 +213,7 @@ Material:
|
||||
- _Dst: 10
|
||||
- _DstBlend: 0
|
||||
- _DstBlendAlpha: 0
|
||||
- _EdgeValue: 0.7824126
|
||||
- _EdgeValue: 0.9979626
|
||||
- _EnvironmentReflections: 1
|
||||
- _FNLfanxiangkaiguan: 0
|
||||
- _Face: 1
|
||||
@@ -258,7 +258,7 @@ Material:
|
||||
- _Mask_scale: 1
|
||||
- _Metallic: 0
|
||||
- _OcclusionStrength: 1
|
||||
- _Opacity: 0.21758741
|
||||
- _Opacity: 0.002037406
|
||||
- _Parallax: 0.005
|
||||
- _Pass: 0
|
||||
- _QueueOffset: 0
|
||||
|
||||
BIN
Assets/FR2_Cache.asset
LFS
BIN
Assets/FR2_Cache.asset
LFS
Binary file not shown.
Binary file not shown.
27
Assets/Plugins/Android/AndroidManifest.xml
Normal file
27
Assets/Plugins/Android/AndroidManifest.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<application>
|
||||
<!--Used when Application Entry is set to Activity, otherwise remove this activity block-->
|
||||
<activity android:name="com.unity3d.player.UnityPlayerActivity"
|
||||
android:theme="@style/UnityThemeSelector">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
|
||||
</activity>
|
||||
<!--Used when Application Entry is set to GameActivity, otherwise remove this activity block-->
|
||||
<activity android:name="com.unity3d.player.UnityPlayerGameActivity"
|
||||
android:theme="@style/BaseUnityGameActivityTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<data android:scheme="ichni" android:host="mylink" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="unityplayer.UnityActivity" android:value="true" />
|
||||
<meta-data android:name="android.app.lib_name" android:value="game" />
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
7
Assets/Plugins/Android/AndroidManifest.xml.meta
Normal file
7
Assets/Plugins/Android/AndroidManifest.xml.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0409f4966cdb7064eb86f782bd83211b
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2687865bbf8495e42baa80cbd81b5e1c
|
||||
guid: 3439c936224b75c4fb5b2fe75cb78ef9
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a45b5316a1162ac469d7c48144dd4bea
|
||||
guid: 98885f0f2a3659142b5d1fedd2fd87c7
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
|
||||
8
Assets/Plugins/UniTask.meta
Normal file
8
Assets/Plugins/UniTask.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fad8c06945f340a4c856e1cb27cac91c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Editor.meta
Normal file
8
Assets/Plugins/UniTask/Editor.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6f3c07df5efce048874f6abb93a3a96
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
62
Assets/Plugins/UniTask/Editor/SplitterGUILayout.cs
Normal file
62
Assets/Plugins/UniTask/Editor/SplitterGUILayout.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Editor
|
||||
{
|
||||
// reflection call of UnityEditor.SplitterGUILayout
|
||||
internal static class SplitterGUILayout
|
||||
{
|
||||
static BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
|
||||
|
||||
static Lazy<Type> splitterStateType = new Lazy<Type>(() =>
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterState");
|
||||
return type;
|
||||
});
|
||||
|
||||
static Lazy<ConstructorInfo> splitterStateCtor = new Lazy<ConstructorInfo>(() =>
|
||||
{
|
||||
var type = splitterStateType.Value;
|
||||
return type.GetConstructor(flags, null, new Type[] { typeof(float[]), typeof(int[]), typeof(int[]) }, null);
|
||||
});
|
||||
|
||||
static Lazy<Type> splitterGUILayoutType = new Lazy<Type>(() =>
|
||||
{
|
||||
var type = typeof(EditorWindow).Assembly.GetTypes().First(x => x.FullName == "UnityEditor.SplitterGUILayout");
|
||||
return type;
|
||||
});
|
||||
|
||||
static Lazy<MethodInfo> beginVerticalSplit = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = splitterGUILayoutType.Value;
|
||||
return type.GetMethod("BeginVerticalSplit", flags, null, new Type[] { splitterStateType.Value, typeof(GUILayoutOption[]) }, null);
|
||||
});
|
||||
|
||||
static Lazy<MethodInfo> endVerticalSplit = new Lazy<MethodInfo>(() =>
|
||||
{
|
||||
var type = splitterGUILayoutType.Value;
|
||||
return type.GetMethod("EndVerticalSplit", flags, null, Type.EmptyTypes, null);
|
||||
});
|
||||
|
||||
public static object CreateSplitterState(float[] relativeSizes, int[] minSizes, int[] maxSizes)
|
||||
{
|
||||
return splitterStateCtor.Value.Invoke(new object[] { relativeSizes, minSizes, maxSizes });
|
||||
}
|
||||
|
||||
public static void BeginVerticalSplit(object splitterState, params GUILayoutOption[] options)
|
||||
{
|
||||
beginVerticalSplit.Value.Invoke(null, new object[] { splitterState, options });
|
||||
}
|
||||
|
||||
public static void EndVerticalSplit()
|
||||
{
|
||||
endVerticalSplit.Value.Invoke(null, Type.EmptyTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Plugins/UniTask/Editor/SplitterGUILayout.cs.meta
Normal file
11
Assets/Plugins/UniTask/Editor/SplitterGUILayout.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40ef2e46f900131419e869398a8d3c9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/Plugins/UniTask/Editor/UniTask.Editor.asmdef
Normal file
17
Assets/Plugins/UniTask/Editor/UniTask.Editor.asmdef
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "UniTask.Editor",
|
||||
"references": [
|
||||
"UniTask"
|
||||
],
|
||||
"includePlatforms": [
|
||||
"Editor"
|
||||
],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": false,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Plugins/UniTask/Editor/UniTask.Editor.asmdef.meta
Normal file
7
Assets/Plugins/UniTask/Editor/UniTask.Editor.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4129704b5a1a13841ba16f230bf24a57
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
186
Assets/Plugins/UniTask/Editor/UniTaskTrackerTreeView.cs
Normal file
186
Assets/Plugins/UniTask/Editor/UniTaskTrackerTreeView.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
#if UNITY_6000_2_OR_NEWER
|
||||
using TreeView = UnityEditor.IMGUI.Controls.TreeView<int>;
|
||||
using TreeViewItem = UnityEditor.IMGUI.Controls.TreeViewItem<int>;
|
||||
using TreeViewState = UnityEditor.IMGUI.Controls.TreeViewState<int>;
|
||||
#endif
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Editor
|
||||
{
|
||||
public class UniTaskTrackerViewItem : TreeViewItem
|
||||
{
|
||||
static Regex removeHref = new Regex("<a href.+>(.+)</a>", RegexOptions.Compiled);
|
||||
|
||||
public string TaskType { get; set; }
|
||||
public string Elapsed { get; set; }
|
||||
public string Status { get; set; }
|
||||
|
||||
string position;
|
||||
public string Position
|
||||
{
|
||||
get { return position; }
|
||||
set
|
||||
{
|
||||
position = value;
|
||||
PositionFirstLine = GetFirstLine(position);
|
||||
}
|
||||
}
|
||||
|
||||
public string PositionFirstLine { get; private set; }
|
||||
|
||||
static string GetFirstLine(string str)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < str.Length; i++)
|
||||
{
|
||||
if (str[i] == '\r' || str[i] == '\n')
|
||||
{
|
||||
break;
|
||||
}
|
||||
sb.Append(str[i]);
|
||||
}
|
||||
|
||||
return removeHref.Replace(sb.ToString(), "$1");
|
||||
}
|
||||
|
||||
public UniTaskTrackerViewItem(int id) : base(id)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public class UniTaskTrackerTreeView : TreeView
|
||||
{
|
||||
const string sortedColumnIndexStateKey = "UniTaskTrackerTreeView_sortedColumnIndex";
|
||||
|
||||
public IReadOnlyList<TreeViewItem> CurrentBindingItems;
|
||||
|
||||
public UniTaskTrackerTreeView()
|
||||
: this(new TreeViewState(), new MultiColumnHeader(new MultiColumnHeaderState(new[]
|
||||
{
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("TaskType"), width = 20},
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Elapsed"), width = 10},
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Status"), width = 10},
|
||||
new MultiColumnHeaderState.Column() { headerContent = new GUIContent("Position")},
|
||||
})))
|
||||
{
|
||||
}
|
||||
|
||||
UniTaskTrackerTreeView(TreeViewState state, MultiColumnHeader header)
|
||||
: base(state, header)
|
||||
{
|
||||
rowHeight = 20;
|
||||
showAlternatingRowBackgrounds = true;
|
||||
showBorder = true;
|
||||
header.sortingChanged += Header_sortingChanged;
|
||||
|
||||
header.ResizeToFit();
|
||||
Reload();
|
||||
|
||||
header.sortedColumnIndex = SessionState.GetInt(sortedColumnIndexStateKey, 1);
|
||||
}
|
||||
|
||||
public void ReloadAndSort()
|
||||
{
|
||||
var currentSelected = this.state.selectedIDs;
|
||||
Reload();
|
||||
Header_sortingChanged(this.multiColumnHeader);
|
||||
this.state.selectedIDs = currentSelected;
|
||||
}
|
||||
|
||||
private void Header_sortingChanged(MultiColumnHeader multiColumnHeader)
|
||||
{
|
||||
SessionState.SetInt(sortedColumnIndexStateKey, multiColumnHeader.sortedColumnIndex);
|
||||
var index = multiColumnHeader.sortedColumnIndex;
|
||||
var ascending = multiColumnHeader.IsSortedAscending(multiColumnHeader.sortedColumnIndex);
|
||||
|
||||
var items = rootItem.children.Cast<UniTaskTrackerViewItem>();
|
||||
|
||||
IOrderedEnumerable<UniTaskTrackerViewItem> orderedEnumerable;
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => item.TaskType) : items.OrderByDescending(item => item.TaskType);
|
||||
break;
|
||||
case 1:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => double.Parse(item.Elapsed)) : items.OrderByDescending(item => double.Parse(item.Elapsed));
|
||||
break;
|
||||
case 2:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => item.Status) : items.OrderByDescending(item => item.Elapsed);
|
||||
break;
|
||||
case 3:
|
||||
orderedEnumerable = ascending ? items.OrderBy(item => item.Position) : items.OrderByDescending(item => item.PositionFirstLine);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(index), index, null);
|
||||
}
|
||||
|
||||
CurrentBindingItems = rootItem.children = orderedEnumerable.Cast<TreeViewItem>().ToList();
|
||||
BuildRows(rootItem);
|
||||
}
|
||||
|
||||
protected override TreeViewItem BuildRoot()
|
||||
{
|
||||
var root = new TreeViewItem { depth = -1 };
|
||||
|
||||
var children = new List<TreeViewItem>();
|
||||
|
||||
TaskTracker.ForEachActiveTask((trackingId, awaiterType, status, created, stackTrace) =>
|
||||
{
|
||||
children.Add(new UniTaskTrackerViewItem(trackingId) { TaskType = awaiterType, Status = status.ToString(), Elapsed = (DateTime.UtcNow - created).TotalSeconds.ToString("00.00"), Position = stackTrace });
|
||||
});
|
||||
|
||||
CurrentBindingItems = children;
|
||||
root.children = CurrentBindingItems as List<TreeViewItem>;
|
||||
return root;
|
||||
}
|
||||
|
||||
protected override bool CanMultiSelect(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void RowGUI(RowGUIArgs args)
|
||||
{
|
||||
var item = args.item as UniTaskTrackerViewItem;
|
||||
|
||||
for (var visibleColumnIndex = 0; visibleColumnIndex < args.GetNumVisibleColumns(); visibleColumnIndex++)
|
||||
{
|
||||
var rect = args.GetCellRect(visibleColumnIndex);
|
||||
var columnIndex = args.GetColumn(visibleColumnIndex);
|
||||
|
||||
var labelStyle = args.selected ? EditorStyles.whiteLabel : EditorStyles.label;
|
||||
labelStyle.alignment = TextAnchor.MiddleLeft;
|
||||
switch (columnIndex)
|
||||
{
|
||||
case 0:
|
||||
EditorGUI.LabelField(rect, item.TaskType, labelStyle);
|
||||
break;
|
||||
case 1:
|
||||
EditorGUI.LabelField(rect, item.Elapsed, labelStyle);
|
||||
break;
|
||||
case 2:
|
||||
EditorGUI.LabelField(rect, item.Status, labelStyle);
|
||||
break;
|
||||
case 3:
|
||||
EditorGUI.LabelField(rect, item.PositionFirstLine, labelStyle);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Editor/UniTaskTrackerTreeView.cs.meta
Normal file
11
Assets/Plugins/UniTask/Editor/UniTaskTrackerTreeView.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 52e2d973a2156674e8c1c9433ed031f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
209
Assets/Plugins/UniTask/Editor/UniTaskTrackerWindow.cs
Normal file
209
Assets/Plugins/UniTask/Editor/UniTaskTrackerWindow.cs
Normal file
@@ -0,0 +1,209 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Editor
|
||||
{
|
||||
public class UniTaskTrackerWindow : EditorWindow
|
||||
{
|
||||
static int interval;
|
||||
|
||||
static UniTaskTrackerWindow window;
|
||||
|
||||
[MenuItem("Window/UniTask Tracker")]
|
||||
public static void OpenWindow()
|
||||
{
|
||||
if (window != null)
|
||||
{
|
||||
window.Close();
|
||||
}
|
||||
|
||||
// will called OnEnable(singleton instance will be set).
|
||||
GetWindow<UniTaskTrackerWindow>("UniTask Tracker").Show();
|
||||
}
|
||||
|
||||
static readonly GUILayoutOption[] EmptyLayoutOption = new GUILayoutOption[0];
|
||||
|
||||
UniTaskTrackerTreeView treeView;
|
||||
object splitterState;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
window = this; // set singleton.
|
||||
splitterState = SplitterGUILayout.CreateSplitterState(new float[] { 75f, 25f }, new int[] { 32, 32 }, null);
|
||||
treeView = new UniTaskTrackerTreeView();
|
||||
TaskTracker.EditorEnableState.EnableAutoReload = EditorPrefs.GetBool(TaskTracker.EnableAutoReloadKey, false);
|
||||
TaskTracker.EditorEnableState.EnableTracking = EditorPrefs.GetBool(TaskTracker.EnableTrackingKey, false);
|
||||
TaskTracker.EditorEnableState.EnableStackTrace = EditorPrefs.GetBool(TaskTracker.EnableStackTraceKey, false);
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
// Head
|
||||
RenderHeadPanel();
|
||||
|
||||
// Splittable
|
||||
SplitterGUILayout.BeginVerticalSplit(this.splitterState, EmptyLayoutOption);
|
||||
{
|
||||
// Column Tabble
|
||||
RenderTable();
|
||||
|
||||
// StackTrace details
|
||||
RenderDetailsPanel();
|
||||
}
|
||||
SplitterGUILayout.EndVerticalSplit();
|
||||
}
|
||||
|
||||
#region HeadPanel
|
||||
|
||||
public static bool EnableAutoReload => TaskTracker.EditorEnableState.EnableAutoReload;
|
||||
public static bool EnableTracking => TaskTracker.EditorEnableState.EnableTracking;
|
||||
public static bool EnableStackTrace => TaskTracker.EditorEnableState.EnableStackTrace;
|
||||
static readonly GUIContent EnableAutoReloadHeadContent = EditorGUIUtility.TrTextContent("Enable AutoReload", "Reload automatically.", (Texture)null);
|
||||
static readonly GUIContent ReloadHeadContent = EditorGUIUtility.TrTextContent("Reload", "Reload View.", (Texture)null);
|
||||
static readonly GUIContent GCHeadContent = EditorGUIUtility.TrTextContent("GC.Collect", "Invoke GC.Collect.", (Texture)null);
|
||||
static readonly GUIContent EnableTrackingHeadContent = EditorGUIUtility.TrTextContent("Enable Tracking", "Start to track async/await UniTask. Performance impact: low", (Texture)null);
|
||||
static readonly GUIContent EnableStackTraceHeadContent = EditorGUIUtility.TrTextContent("Enable StackTrace", "Capture StackTrace when task is started. Performance impact: high", (Texture)null);
|
||||
|
||||
// [Enable Tracking] | [Enable StackTrace]
|
||||
void RenderHeadPanel()
|
||||
{
|
||||
EditorGUILayout.BeginVertical(EmptyLayoutOption);
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar, EmptyLayoutOption);
|
||||
|
||||
if (GUILayout.Toggle(EnableAutoReload, EnableAutoReloadHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != EnableAutoReload)
|
||||
{
|
||||
TaskTracker.EditorEnableState.EnableAutoReload = !EnableAutoReload;
|
||||
}
|
||||
|
||||
if (GUILayout.Toggle(EnableTracking, EnableTrackingHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != EnableTracking)
|
||||
{
|
||||
TaskTracker.EditorEnableState.EnableTracking = !EnableTracking;
|
||||
}
|
||||
|
||||
if (GUILayout.Toggle(EnableStackTrace, EnableStackTraceHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption) != EnableStackTrace)
|
||||
{
|
||||
TaskTracker.EditorEnableState.EnableStackTrace = !EnableStackTrace;
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button(ReloadHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption))
|
||||
{
|
||||
TaskTracker.CheckAndResetDirty();
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(GCHeadContent, EditorStyles.toolbarButton, EmptyLayoutOption))
|
||||
{
|
||||
GC.Collect(0);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TableColumn
|
||||
|
||||
Vector2 tableScroll;
|
||||
GUIStyle tableListStyle;
|
||||
|
||||
void RenderTable()
|
||||
{
|
||||
if (tableListStyle == null)
|
||||
{
|
||||
tableListStyle = new GUIStyle("CN Box");
|
||||
tableListStyle.margin.top = 0;
|
||||
tableListStyle.padding.left = 3;
|
||||
}
|
||||
|
||||
EditorGUILayout.BeginVertical(tableListStyle, EmptyLayoutOption);
|
||||
|
||||
this.tableScroll = EditorGUILayout.BeginScrollView(this.tableScroll, new GUILayoutOption[]
|
||||
{
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MaxWidth(2000f)
|
||||
});
|
||||
var controlRect = EditorGUILayout.GetControlRect(new GUILayoutOption[]
|
||||
{
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true)
|
||||
});
|
||||
|
||||
|
||||
treeView?.OnGUI(controlRect);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
EditorGUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (EnableAutoReload)
|
||||
{
|
||||
if (interval++ % 120 == 0)
|
||||
{
|
||||
if (TaskTracker.CheckAndResetDirty())
|
||||
{
|
||||
treeView.ReloadAndSort();
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Details
|
||||
|
||||
static GUIStyle detailsStyle;
|
||||
Vector2 detailsScroll;
|
||||
|
||||
void RenderDetailsPanel()
|
||||
{
|
||||
if (detailsStyle == null)
|
||||
{
|
||||
detailsStyle = new GUIStyle("CN Message");
|
||||
detailsStyle.wordWrap = false;
|
||||
detailsStyle.stretchHeight = true;
|
||||
detailsStyle.margin.right = 15;
|
||||
}
|
||||
|
||||
string message = "";
|
||||
var selected = treeView.state.selectedIDs;
|
||||
if (selected.Count > 0)
|
||||
{
|
||||
var first = selected[0];
|
||||
var item = treeView.CurrentBindingItems.FirstOrDefault(x => x.id == first) as UniTaskTrackerViewItem;
|
||||
if (item != null)
|
||||
{
|
||||
message = item.Position;
|
||||
}
|
||||
}
|
||||
|
||||
detailsScroll = EditorGUILayout.BeginScrollView(this.detailsScroll, EmptyLayoutOption);
|
||||
var vector = detailsStyle.CalcSize(new GUIContent(message));
|
||||
EditorGUILayout.SelectableLabel(message, detailsStyle, new GUILayoutOption[]
|
||||
{
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MinWidth(vector.x),
|
||||
GUILayout.MinHeight(vector.y)
|
||||
});
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Plugins/UniTask/Editor/UniTaskTrackerWindow.cs.meta
Normal file
11
Assets/Plugins/UniTask/Editor/UniTaskTrackerWindow.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5bee3e3860e37484aa3b861bf76d129f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime.meta
Normal file
8
Assets/Plugins/UniTask/Runtime.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c4f9b8a894ef584587a8cec0ee08362
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
245
Assets/Plugins/UniTask/Runtime/AsyncLazy.cs
Normal file
245
Assets/Plugins/UniTask/Runtime/AsyncLazy.cs
Normal file
@@ -0,0 +1,245 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public class AsyncLazy
|
||||
{
|
||||
static Action<object> continuation = SetCompletionSource;
|
||||
|
||||
Func<UniTask> taskFactory;
|
||||
UniTaskCompletionSource completionSource;
|
||||
UniTask.Awaiter awaiter;
|
||||
|
||||
object syncLock;
|
||||
bool initialized;
|
||||
|
||||
public AsyncLazy(Func<UniTask> taskFactory)
|
||||
{
|
||||
this.taskFactory = taskFactory;
|
||||
this.completionSource = new UniTaskCompletionSource();
|
||||
this.syncLock = new object();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
internal AsyncLazy(UniTask task)
|
||||
{
|
||||
this.taskFactory = null;
|
||||
this.completionSource = new UniTaskCompletionSource();
|
||||
this.syncLock = null;
|
||||
this.initialized = true;
|
||||
|
||||
var awaiter = task.GetAwaiter();
|
||||
if (awaiter.IsCompleted)
|
||||
{
|
||||
SetCompletionSource(awaiter);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.awaiter = awaiter;
|
||||
awaiter.SourceOnCompleted(continuation, this);
|
||||
}
|
||||
}
|
||||
|
||||
public UniTask Task
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureInitialized();
|
||||
return completionSource.Task;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public UniTask.Awaiter GetAwaiter() => Task.GetAwaiter();
|
||||
|
||||
void EnsureInitialized()
|
||||
{
|
||||
if (Volatile.Read(ref initialized))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureInitializedCore();
|
||||
}
|
||||
|
||||
void EnsureInitializedCore()
|
||||
{
|
||||
lock (syncLock)
|
||||
{
|
||||
if (!Volatile.Read(ref initialized))
|
||||
{
|
||||
var f = Interlocked.Exchange(ref taskFactory, null);
|
||||
if (f != null)
|
||||
{
|
||||
var task = f();
|
||||
var awaiter = task.GetAwaiter();
|
||||
if (awaiter.IsCompleted)
|
||||
{
|
||||
SetCompletionSource(awaiter);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.awaiter = awaiter;
|
||||
awaiter.SourceOnCompleted(continuation, this);
|
||||
}
|
||||
|
||||
Volatile.Write(ref initialized, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetCompletionSource(in UniTask.Awaiter awaiter)
|
||||
{
|
||||
try
|
||||
{
|
||||
awaiter.GetResult();
|
||||
completionSource.TrySetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
completionSource.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void SetCompletionSource(object state)
|
||||
{
|
||||
var self = (AsyncLazy)state;
|
||||
try
|
||||
{
|
||||
self.awaiter.GetResult();
|
||||
self.completionSource.TrySetResult();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
self.completionSource.TrySetException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
self.awaiter = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class AsyncLazy<T>
|
||||
{
|
||||
static Action<object> continuation = SetCompletionSource;
|
||||
|
||||
Func<UniTask<T>> taskFactory;
|
||||
UniTaskCompletionSource<T> completionSource;
|
||||
UniTask<T>.Awaiter awaiter;
|
||||
|
||||
object syncLock;
|
||||
bool initialized;
|
||||
|
||||
public AsyncLazy(Func<UniTask<T>> taskFactory)
|
||||
{
|
||||
this.taskFactory = taskFactory;
|
||||
this.completionSource = new UniTaskCompletionSource<T>();
|
||||
this.syncLock = new object();
|
||||
this.initialized = false;
|
||||
}
|
||||
|
||||
internal AsyncLazy(UniTask<T> task)
|
||||
{
|
||||
this.taskFactory = null;
|
||||
this.completionSource = new UniTaskCompletionSource<T>();
|
||||
this.syncLock = null;
|
||||
this.initialized = true;
|
||||
|
||||
var awaiter = task.GetAwaiter();
|
||||
if (awaiter.IsCompleted)
|
||||
{
|
||||
SetCompletionSource(awaiter);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.awaiter = awaiter;
|
||||
awaiter.SourceOnCompleted(continuation, this);
|
||||
}
|
||||
}
|
||||
|
||||
public UniTask<T> Task
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureInitialized();
|
||||
return completionSource.Task;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public UniTask<T>.Awaiter GetAwaiter() => Task.GetAwaiter();
|
||||
|
||||
void EnsureInitialized()
|
||||
{
|
||||
if (Volatile.Read(ref initialized))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureInitializedCore();
|
||||
}
|
||||
|
||||
void EnsureInitializedCore()
|
||||
{
|
||||
lock (syncLock)
|
||||
{
|
||||
if (!Volatile.Read(ref initialized))
|
||||
{
|
||||
var f = Interlocked.Exchange(ref taskFactory, null);
|
||||
if (f != null)
|
||||
{
|
||||
var task = f();
|
||||
var awaiter = task.GetAwaiter();
|
||||
if (awaiter.IsCompleted)
|
||||
{
|
||||
SetCompletionSource(awaiter);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.awaiter = awaiter;
|
||||
awaiter.SourceOnCompleted(continuation, this);
|
||||
}
|
||||
|
||||
Volatile.Write(ref initialized, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SetCompletionSource(in UniTask<T>.Awaiter awaiter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = awaiter.GetResult();
|
||||
completionSource.TrySetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
completionSource.TrySetException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
static void SetCompletionSource(object state)
|
||||
{
|
||||
var self = (AsyncLazy<T>)state;
|
||||
try
|
||||
{
|
||||
var result = self.awaiter.GetResult();
|
||||
self.completionSource.TrySetResult(result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
self.completionSource.TrySetException(ex);
|
||||
}
|
||||
finally
|
||||
{
|
||||
self.awaiter = default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Runtime/AsyncLazy.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/AsyncLazy.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 01d1404ca421466419a7db7340ff5e77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
644
Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs
Normal file
644
Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs
Normal file
@@ -0,0 +1,644 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public interface IReadOnlyAsyncReactiveProperty<T> : IUniTaskAsyncEnumerable<T>
|
||||
{
|
||||
T Value { get; }
|
||||
IUniTaskAsyncEnumerable<T> WithoutCurrent();
|
||||
UniTask<T> WaitAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IAsyncReactiveProperty<T> : IReadOnlyAsyncReactiveProperty<T>
|
||||
{
|
||||
new T Value { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class AsyncReactiveProperty<T> : IAsyncReactiveProperty<T>, IDisposable
|
||||
{
|
||||
TriggerEvent<T> triggerEvent;
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
[UnityEngine.SerializeField]
|
||||
#endif
|
||||
T latestValue;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return latestValue;
|
||||
}
|
||||
set
|
||||
{
|
||||
this.latestValue = value;
|
||||
triggerEvent.SetResult(value);
|
||||
}
|
||||
}
|
||||
|
||||
public AsyncReactiveProperty(T value)
|
||||
{
|
||||
this.latestValue = value;
|
||||
this.triggerEvent = default;
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerable<T> WithoutCurrent()
|
||||
{
|
||||
return new WithoutCurrentEnumerable(this);
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken)
|
||||
{
|
||||
return new Enumerator(this, cancellationToken, true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
triggerEvent.SetCompleted();
|
||||
}
|
||||
|
||||
public static implicit operator T(AsyncReactiveProperty<T> value)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (isValueType) return latestValue.ToString();
|
||||
return latestValue?.ToString();
|
||||
}
|
||||
|
||||
public UniTask<T> WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new UniTask<T>(WaitAsyncSource.Create(this, cancellationToken, out var token), token);
|
||||
}
|
||||
|
||||
static bool isValueType;
|
||||
|
||||
static AsyncReactiveProperty()
|
||||
{
|
||||
isValueType = typeof(T).IsValueType;
|
||||
}
|
||||
|
||||
sealed class WaitAsyncSource : IUniTaskSource<T>, ITriggerHandler<T>, ITaskPoolNode<WaitAsyncSource>
|
||||
{
|
||||
static Action<object> cancellationCallback = CancellationCallback;
|
||||
|
||||
static TaskPool<WaitAsyncSource> pool;
|
||||
WaitAsyncSource nextNode;
|
||||
ref WaitAsyncSource ITaskPoolNode<WaitAsyncSource>.NextNode => ref nextNode;
|
||||
|
||||
static WaitAsyncSource()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(WaitAsyncSource), () => pool.Size);
|
||||
}
|
||||
|
||||
AsyncReactiveProperty<T> parent;
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration cancellationTokenRegistration;
|
||||
UniTaskCompletionSourceCore<T> core;
|
||||
|
||||
WaitAsyncSource()
|
||||
{
|
||||
}
|
||||
|
||||
public static IUniTaskSource<T> Create(AsyncReactiveProperty<T> parent, CancellationToken cancellationToken, out short token)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
|
||||
}
|
||||
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new WaitAsyncSource();
|
||||
}
|
||||
|
||||
result.parent = parent;
|
||||
result.cancellationToken = cancellationToken;
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, result);
|
||||
}
|
||||
|
||||
result.parent.triggerEvent.Add(result);
|
||||
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
token = result.core.Version;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
cancellationTokenRegistration.Dispose();
|
||||
cancellationTokenRegistration = default;
|
||||
parent.triggerEvent.Remove(this);
|
||||
parent = null;
|
||||
cancellationToken = default;
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
|
||||
static void CancellationCallback(object state)
|
||||
{
|
||||
var self = (WaitAsyncSource)state;
|
||||
self.OnCanceled(self.cancellationToken);
|
||||
}
|
||||
|
||||
// IUniTaskSource
|
||||
|
||||
public T GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
GetResult(token);
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
// ITriggerHandler
|
||||
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
|
||||
|
||||
public void OnCanceled(CancellationToken cancellationToken)
|
||||
{
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
// Complete as Cancel.
|
||||
core.TrySetCanceled(CancellationToken.None);
|
||||
}
|
||||
|
||||
public void OnError(Exception ex)
|
||||
{
|
||||
core.TrySetException(ex);
|
||||
}
|
||||
|
||||
public void OnNext(T value)
|
||||
{
|
||||
core.TrySetResult(value);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T>
|
||||
{
|
||||
readonly AsyncReactiveProperty<T> parent;
|
||||
|
||||
public WithoutCurrentEnumerable(AsyncReactiveProperty<T> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new Enumerator(parent, cancellationToken, false);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, ITriggerHandler<T>
|
||||
{
|
||||
static Action<object> cancellationCallback = CancellationCallback;
|
||||
|
||||
readonly AsyncReactiveProperty<T> parent;
|
||||
readonly CancellationToken cancellationToken;
|
||||
readonly CancellationTokenRegistration cancellationTokenRegistration;
|
||||
T value;
|
||||
bool isDisposed;
|
||||
bool firstCall;
|
||||
|
||||
public Enumerator(AsyncReactiveProperty<T> parent, CancellationToken cancellationToken, bool publishCurrentValue)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.cancellationToken = cancellationToken;
|
||||
this.firstCall = publishCurrentValue;
|
||||
|
||||
parent.triggerEvent.Add(this);
|
||||
TaskTracker.TrackActiveTask(this, 3);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this);
|
||||
}
|
||||
}
|
||||
|
||||
public T Current => value;
|
||||
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
|
||||
|
||||
public UniTask<bool> MoveNextAsync()
|
||||
{
|
||||
// raise latest value on first call.
|
||||
if (firstCall)
|
||||
{
|
||||
firstCall = false;
|
||||
value = parent.Value;
|
||||
return CompletedTasks.True;
|
||||
}
|
||||
|
||||
completionSource.Reset();
|
||||
return new UniTask<bool>(this, completionSource.Version);
|
||||
}
|
||||
|
||||
public UniTask DisposeAsync()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
isDisposed = true;
|
||||
TaskTracker.RemoveTracking(this);
|
||||
completionSource.TrySetCanceled(cancellationToken);
|
||||
parent.triggerEvent.Remove(this);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public void OnNext(T value)
|
||||
{
|
||||
this.value = value;
|
||||
completionSource.TrySetResult(true);
|
||||
}
|
||||
|
||||
public void OnCanceled(CancellationToken cancellationToken)
|
||||
{
|
||||
DisposeAsync().Forget();
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
completionSource.TrySetResult(false);
|
||||
}
|
||||
|
||||
public void OnError(Exception ex)
|
||||
{
|
||||
completionSource.TrySetException(ex);
|
||||
}
|
||||
|
||||
static void CancellationCallback(object state)
|
||||
{
|
||||
var self = (Enumerator)state;
|
||||
self.DisposeAsync().Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ReadOnlyAsyncReactiveProperty<T> : IReadOnlyAsyncReactiveProperty<T>, IDisposable
|
||||
{
|
||||
TriggerEvent<T> triggerEvent;
|
||||
|
||||
T latestValue;
|
||||
IUniTaskAsyncEnumerator<T> enumerator;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
return latestValue;
|
||||
}
|
||||
}
|
||||
|
||||
public ReadOnlyAsyncReactiveProperty(T initialValue, IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken)
|
||||
{
|
||||
latestValue = initialValue;
|
||||
ConsumeEnumerator(source, cancellationToken).Forget();
|
||||
}
|
||||
|
||||
public ReadOnlyAsyncReactiveProperty(IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken)
|
||||
{
|
||||
ConsumeEnumerator(source, cancellationToken).Forget();
|
||||
}
|
||||
|
||||
async UniTaskVoid ConsumeEnumerator(IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken)
|
||||
{
|
||||
enumerator = source.GetAsyncEnumerator(cancellationToken);
|
||||
try
|
||||
{
|
||||
while (await enumerator.MoveNextAsync())
|
||||
{
|
||||
var value = enumerator.Current;
|
||||
this.latestValue = value;
|
||||
triggerEvent.SetResult(value);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
await enumerator.DisposeAsync();
|
||||
enumerator = null;
|
||||
}
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerable<T> WithoutCurrent()
|
||||
{
|
||||
return new WithoutCurrentEnumerable(this);
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken)
|
||||
{
|
||||
return new Enumerator(this, cancellationToken, true);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (enumerator != null)
|
||||
{
|
||||
enumerator.DisposeAsync().Forget();
|
||||
}
|
||||
|
||||
triggerEvent.SetCompleted();
|
||||
}
|
||||
|
||||
public static implicit operator T(ReadOnlyAsyncReactiveProperty<T> value)
|
||||
{
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
if (isValueType) return latestValue.ToString();
|
||||
return latestValue?.ToString();
|
||||
}
|
||||
|
||||
public UniTask<T> WaitAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new UniTask<T>(WaitAsyncSource.Create(this, cancellationToken, out var token), token);
|
||||
}
|
||||
|
||||
static bool isValueType;
|
||||
|
||||
static ReadOnlyAsyncReactiveProperty()
|
||||
{
|
||||
isValueType = typeof(T).IsValueType;
|
||||
}
|
||||
|
||||
sealed class WaitAsyncSource : IUniTaskSource<T>, ITriggerHandler<T>, ITaskPoolNode<WaitAsyncSource>
|
||||
{
|
||||
static Action<object> cancellationCallback = CancellationCallback;
|
||||
|
||||
static TaskPool<WaitAsyncSource> pool;
|
||||
WaitAsyncSource nextNode;
|
||||
ref WaitAsyncSource ITaskPoolNode<WaitAsyncSource>.NextNode => ref nextNode;
|
||||
|
||||
static WaitAsyncSource()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(WaitAsyncSource), () => pool.Size);
|
||||
}
|
||||
|
||||
ReadOnlyAsyncReactiveProperty<T> parent;
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration cancellationTokenRegistration;
|
||||
UniTaskCompletionSourceCore<T> core;
|
||||
|
||||
WaitAsyncSource()
|
||||
{
|
||||
}
|
||||
|
||||
public static IUniTaskSource<T> Create(ReadOnlyAsyncReactiveProperty<T> parent, CancellationToken cancellationToken, out short token)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
|
||||
}
|
||||
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new WaitAsyncSource();
|
||||
}
|
||||
|
||||
result.parent = parent;
|
||||
result.cancellationToken = cancellationToken;
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, result);
|
||||
}
|
||||
|
||||
result.parent.triggerEvent.Add(result);
|
||||
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
token = result.core.Version;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
cancellationTokenRegistration.Dispose();
|
||||
cancellationTokenRegistration = default;
|
||||
parent.triggerEvent.Remove(this);
|
||||
parent = null;
|
||||
cancellationToken = default;
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
|
||||
static void CancellationCallback(object state)
|
||||
{
|
||||
var self = (WaitAsyncSource)state;
|
||||
self.OnCanceled(self.cancellationToken);
|
||||
}
|
||||
|
||||
// IUniTaskSource
|
||||
|
||||
public T GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
GetResult(token);
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
// ITriggerHandler
|
||||
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
|
||||
|
||||
public void OnCanceled(CancellationToken cancellationToken)
|
||||
{
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
// Complete as Cancel.
|
||||
core.TrySetCanceled(CancellationToken.None);
|
||||
}
|
||||
|
||||
public void OnError(Exception ex)
|
||||
{
|
||||
core.TrySetException(ex);
|
||||
}
|
||||
|
||||
public void OnNext(T value)
|
||||
{
|
||||
core.TrySetResult(value);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class WithoutCurrentEnumerable : IUniTaskAsyncEnumerable<T>
|
||||
{
|
||||
readonly ReadOnlyAsyncReactiveProperty<T> parent;
|
||||
|
||||
public WithoutCurrentEnumerable(ReadOnlyAsyncReactiveProperty<T> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new Enumerator(parent, cancellationToken, false);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Enumerator : MoveNextSource, IUniTaskAsyncEnumerator<T>, ITriggerHandler<T>
|
||||
{
|
||||
static Action<object> cancellationCallback = CancellationCallback;
|
||||
|
||||
readonly ReadOnlyAsyncReactiveProperty<T> parent;
|
||||
readonly CancellationToken cancellationToken;
|
||||
readonly CancellationTokenRegistration cancellationTokenRegistration;
|
||||
T value;
|
||||
bool isDisposed;
|
||||
bool firstCall;
|
||||
|
||||
public Enumerator(ReadOnlyAsyncReactiveProperty<T> parent, CancellationToken cancellationToken, bool publishCurrentValue)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.cancellationToken = cancellationToken;
|
||||
this.firstCall = publishCurrentValue;
|
||||
|
||||
parent.triggerEvent.Add(this);
|
||||
TaskTracker.TrackActiveTask(this, 3);
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationCallback, this);
|
||||
}
|
||||
}
|
||||
|
||||
public T Current => value;
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Prev { get; set; }
|
||||
ITriggerHandler<T> ITriggerHandler<T>.Next { get; set; }
|
||||
|
||||
public UniTask<bool> MoveNextAsync()
|
||||
{
|
||||
// raise latest value on first call.
|
||||
if (firstCall)
|
||||
{
|
||||
firstCall = false;
|
||||
value = parent.Value;
|
||||
return CompletedTasks.True;
|
||||
}
|
||||
|
||||
completionSource.Reset();
|
||||
return new UniTask<bool>(this, completionSource.Version);
|
||||
}
|
||||
|
||||
public UniTask DisposeAsync()
|
||||
{
|
||||
if (!isDisposed)
|
||||
{
|
||||
isDisposed = true;
|
||||
TaskTracker.RemoveTracking(this);
|
||||
completionSource.TrySetCanceled(cancellationToken);
|
||||
parent.triggerEvent.Remove(this);
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public void OnNext(T value)
|
||||
{
|
||||
this.value = value;
|
||||
completionSource.TrySetResult(true);
|
||||
}
|
||||
|
||||
public void OnCanceled(CancellationToken cancellationToken)
|
||||
{
|
||||
DisposeAsync().Forget();
|
||||
}
|
||||
|
||||
public void OnCompleted()
|
||||
{
|
||||
completionSource.TrySetResult(false);
|
||||
}
|
||||
|
||||
public void OnError(Exception ex)
|
||||
{
|
||||
completionSource.TrySetException(ex);
|
||||
}
|
||||
|
||||
static void CancellationCallback(object state)
|
||||
{
|
||||
var self = (Enumerator)state;
|
||||
self.DisposeAsync().Forget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class StateExtensions
|
||||
{
|
||||
public static ReadOnlyAsyncReactiveProperty<T> ToReadOnlyAsyncReactiveProperty<T>(this IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken)
|
||||
{
|
||||
return new ReadOnlyAsyncReactiveProperty<T>(source, cancellationToken);
|
||||
}
|
||||
|
||||
public static ReadOnlyAsyncReactiveProperty<T> ToReadOnlyAsyncReactiveProperty<T>(this IUniTaskAsyncEnumerable<T> source, T initialValue, CancellationToken cancellationToken)
|
||||
{
|
||||
return new ReadOnlyAsyncReactiveProperty<T>(initialValue, source, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/AsyncReactiveProperty.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ef320b87f537ee4fb2282e765dc6166
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
26
Assets/Plugins/UniTask/Runtime/AsyncUnit.cs
Normal file
26
Assets/Plugins/UniTask/Runtime/AsyncUnit.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or
|
||||
|
||||
using System;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public readonly struct AsyncUnit : IEquatable<AsyncUnit>
|
||||
{
|
||||
public static readonly AsyncUnit Default = new AsyncUnit();
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool Equals(AsyncUnit other)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "()";
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Runtime/AsyncUnit.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/AsyncUnit.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f95ac245430d304bb5128d13b6becc8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,23 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public class CancellationTokenEqualityComparer : IEqualityComparer<CancellationToken>
|
||||
{
|
||||
public static readonly IEqualityComparer<CancellationToken> Default = new CancellationTokenEqualityComparer();
|
||||
|
||||
public bool Equals(CancellationToken x, CancellationToken y)
|
||||
{
|
||||
return x.Equals(y);
|
||||
}
|
||||
|
||||
public int GetHashCode(CancellationToken obj)
|
||||
{
|
||||
return obj.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d739f510b125b74fa7290ac4335e46e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
182
Assets/Plugins/UniTask/Runtime/CancellationTokenExtensions.cs
Normal file
182
Assets/Plugins/UniTask/Runtime/CancellationTokenExtensions.cs
Normal file
@@ -0,0 +1,182 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static class CancellationTokenExtensions
|
||||
{
|
||||
static readonly Action<object> cancellationTokenCallback = Callback;
|
||||
static readonly Action<object> disposeCallback = DisposeCallback;
|
||||
|
||||
public static CancellationToken ToCancellationToken(this UniTask task)
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
ToCancellationTokenCore(task, cts).Forget();
|
||||
return cts.Token;
|
||||
}
|
||||
|
||||
public static CancellationToken ToCancellationToken(this UniTask task, CancellationToken linkToken)
|
||||
{
|
||||
if (linkToken.IsCancellationRequested)
|
||||
{
|
||||
return linkToken;
|
||||
}
|
||||
|
||||
if (!linkToken.CanBeCanceled)
|
||||
{
|
||||
return ToCancellationToken(task);
|
||||
}
|
||||
|
||||
var cts = CancellationTokenSource.CreateLinkedTokenSource(linkToken);
|
||||
ToCancellationTokenCore(task, cts).Forget();
|
||||
|
||||
return cts.Token;
|
||||
}
|
||||
|
||||
public static CancellationToken ToCancellationToken<T>(this UniTask<T> task)
|
||||
{
|
||||
return ToCancellationToken(task.AsUniTask());
|
||||
}
|
||||
|
||||
public static CancellationToken ToCancellationToken<T>(this UniTask<T> task, CancellationToken linkToken)
|
||||
{
|
||||
return ToCancellationToken(task.AsUniTask(), linkToken);
|
||||
}
|
||||
|
||||
static async UniTaskVoid ToCancellationTokenCore(UniTask task, CancellationTokenSource cts)
|
||||
{
|
||||
try
|
||||
{
|
||||
await task;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UniTaskScheduler.PublishUnobservedTaskException(ex);
|
||||
}
|
||||
cts.Cancel();
|
||||
cts.Dispose();
|
||||
}
|
||||
|
||||
public static (UniTask, CancellationTokenRegistration) ToUniTask(this CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return (UniTask.FromCanceled(cancellationToken), default(CancellationTokenRegistration));
|
||||
}
|
||||
|
||||
var promise = new UniTaskCompletionSource();
|
||||
return (promise.Task, cancellationToken.RegisterWithoutCaptureExecutionContext(cancellationTokenCallback, promise));
|
||||
}
|
||||
|
||||
static void Callback(object state)
|
||||
{
|
||||
var promise = (UniTaskCompletionSource)state;
|
||||
promise.TrySetResult();
|
||||
}
|
||||
|
||||
public static CancellationTokenAwaitable WaitUntilCanceled(this CancellationToken cancellationToken)
|
||||
{
|
||||
return new CancellationTokenAwaitable(cancellationToken);
|
||||
}
|
||||
|
||||
public static CancellationTokenRegistration RegisterWithoutCaptureExecutionContext(this CancellationToken cancellationToken, Action callback)
|
||||
{
|
||||
var restoreFlow = false;
|
||||
if (!ExecutionContext.IsFlowSuppressed())
|
||||
{
|
||||
ExecutionContext.SuppressFlow();
|
||||
restoreFlow = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return cancellationToken.Register(callback, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (restoreFlow)
|
||||
{
|
||||
ExecutionContext.RestoreFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CancellationTokenRegistration RegisterWithoutCaptureExecutionContext(this CancellationToken cancellationToken, Action<object> callback, object state)
|
||||
{
|
||||
var restoreFlow = false;
|
||||
if (!ExecutionContext.IsFlowSuppressed())
|
||||
{
|
||||
ExecutionContext.SuppressFlow();
|
||||
restoreFlow = true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return cancellationToken.Register(callback, state, false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (restoreFlow)
|
||||
{
|
||||
ExecutionContext.RestoreFlow();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static CancellationTokenRegistration AddTo(this IDisposable disposable, CancellationToken cancellationToken)
|
||||
{
|
||||
return cancellationToken.RegisterWithoutCaptureExecutionContext(disposeCallback, disposable);
|
||||
}
|
||||
|
||||
static void DisposeCallback(object state)
|
||||
{
|
||||
var d = (IDisposable)state;
|
||||
d.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public struct CancellationTokenAwaitable
|
||||
{
|
||||
CancellationToken cancellationToken;
|
||||
|
||||
public CancellationTokenAwaitable(CancellationToken cancellationToken)
|
||||
{
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public Awaiter GetAwaiter()
|
||||
{
|
||||
return new Awaiter(cancellationToken);
|
||||
}
|
||||
|
||||
public struct Awaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
CancellationToken cancellationToken;
|
||||
|
||||
public Awaiter(CancellationToken cancellationToken)
|
||||
{
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public bool IsCompleted => !cancellationToken.CanBeCanceled || cancellationToken.IsCancellationRequested;
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
cancellationToken.RegisterWithoutCaptureExecutionContext(continuation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4be7209f04146bd45ac5ee775a5f7c26
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using Cysharp.Threading.Tasks.Triggers;
|
||||
using System;
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
|
||||
public static partial class CancellationTokenSourceExtensions
|
||||
{
|
||||
readonly static Action<object> CancelCancellationTokenSourceStateDelegate = new Action<object>(CancelCancellationTokenSourceState);
|
||||
|
||||
static void CancelCancellationTokenSourceState(object state)
|
||||
{
|
||||
var cts = (CancellationTokenSource)state;
|
||||
cts.Cancel();
|
||||
}
|
||||
|
||||
public static IDisposable CancelAfterSlim(this CancellationTokenSource cts, int millisecondsDelay, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||||
{
|
||||
return CancelAfterSlim(cts, TimeSpan.FromMilliseconds(millisecondsDelay), delayType, delayTiming);
|
||||
}
|
||||
|
||||
public static IDisposable CancelAfterSlim(this CancellationTokenSource cts, TimeSpan delayTimeSpan, DelayType delayType = DelayType.DeltaTime, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update)
|
||||
{
|
||||
return PlayerLoopTimer.StartNew(delayTimeSpan, false, delayType, delayTiming, cts.Token, CancelCancellationTokenSourceStateDelegate, cts);
|
||||
}
|
||||
|
||||
public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, Component component)
|
||||
{
|
||||
RegisterRaiseCancelOnDestroy(cts, component.gameObject);
|
||||
}
|
||||
|
||||
public static void RegisterRaiseCancelOnDestroy(this CancellationTokenSource cts, GameObject gameObject)
|
||||
{
|
||||
var trigger = gameObject.GetAsyncDestroyTrigger();
|
||||
trigger.CancellationToken.RegisterWithoutCaptureExecutionContext(CancelCancellationTokenSourceStateDelegate, cts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22d85d07f1e70ab42a7a4c25bd65e661
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
450
Assets/Plugins/UniTask/Runtime/Channel.cs
Normal file
450
Assets/Plugins/UniTask/Runtime/Channel.cs
Normal file
@@ -0,0 +1,450 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static class Channel
|
||||
{
|
||||
public static Channel<T> CreateSingleConsumerUnbounded<T>()
|
||||
{
|
||||
return new SingleConsumerUnboundedChannel<T>();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class Channel<TWrite, TRead>
|
||||
{
|
||||
public ChannelReader<TRead> Reader { get; protected set; }
|
||||
public ChannelWriter<TWrite> Writer { get; protected set; }
|
||||
|
||||
public static implicit operator ChannelReader<TRead>(Channel<TWrite, TRead> channel) => channel.Reader;
|
||||
public static implicit operator ChannelWriter<TWrite>(Channel<TWrite, TRead> channel) => channel.Writer;
|
||||
}
|
||||
|
||||
public abstract class Channel<T> : Channel<T, T>
|
||||
{
|
||||
}
|
||||
|
||||
public abstract class ChannelReader<T>
|
||||
{
|
||||
public abstract bool TryRead(out T item);
|
||||
public abstract UniTask<bool> WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
|
||||
public abstract UniTask Completion { get; }
|
||||
|
||||
public virtual UniTask<T> ReadAsync(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (this.TryRead(out var item))
|
||||
{
|
||||
return UniTask.FromResult(item);
|
||||
}
|
||||
|
||||
return ReadAsyncCore(cancellationToken);
|
||||
}
|
||||
|
||||
async UniTask<T> ReadAsyncCore(CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
if (await WaitToReadAsync(cancellationToken))
|
||||
{
|
||||
if (TryRead(out var item))
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ChannelClosedException();
|
||||
}
|
||||
|
||||
public abstract IUniTaskAsyncEnumerable<T> ReadAllAsync(CancellationToken cancellationToken = default(CancellationToken));
|
||||
}
|
||||
|
||||
public abstract class ChannelWriter<T>
|
||||
{
|
||||
public abstract bool TryWrite(T item);
|
||||
public abstract bool TryComplete(Exception error = null);
|
||||
|
||||
public void Complete(Exception error = null)
|
||||
{
|
||||
if (!TryComplete(error))
|
||||
{
|
||||
throw new ChannelClosedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class ChannelClosedException : InvalidOperationException
|
||||
{
|
||||
public ChannelClosedException() :
|
||||
base("Channel is already closed.")
|
||||
{ }
|
||||
|
||||
public ChannelClosedException(string message) : base(message) { }
|
||||
|
||||
public ChannelClosedException(Exception innerException) :
|
||||
base("Channel is already closed", innerException)
|
||||
{ }
|
||||
|
||||
public ChannelClosedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
|
||||
internal class SingleConsumerUnboundedChannel<T> : Channel<T>
|
||||
{
|
||||
readonly Queue<T> items;
|
||||
readonly SingleConsumerUnboundedChannelReader readerSource;
|
||||
UniTaskCompletionSource completedTaskSource;
|
||||
UniTask completedTask;
|
||||
|
||||
Exception completionError;
|
||||
bool closed;
|
||||
|
||||
public SingleConsumerUnboundedChannel()
|
||||
{
|
||||
items = new Queue<T>();
|
||||
Writer = new SingleConsumerUnboundedChannelWriter(this);
|
||||
readerSource = new SingleConsumerUnboundedChannelReader(this);
|
||||
Reader = readerSource;
|
||||
}
|
||||
|
||||
sealed class SingleConsumerUnboundedChannelWriter : ChannelWriter<T>
|
||||
{
|
||||
readonly SingleConsumerUnboundedChannel<T> parent;
|
||||
|
||||
public SingleConsumerUnboundedChannelWriter(SingleConsumerUnboundedChannel<T> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public override bool TryWrite(T item)
|
||||
{
|
||||
bool waiting;
|
||||
lock (parent.items)
|
||||
{
|
||||
if (parent.closed) return false;
|
||||
|
||||
parent.items.Enqueue(item);
|
||||
waiting = parent.readerSource.isWaiting;
|
||||
}
|
||||
|
||||
if (waiting)
|
||||
{
|
||||
parent.readerSource.SingalContinuation();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool TryComplete(Exception error = null)
|
||||
{
|
||||
bool waiting;
|
||||
lock (parent.items)
|
||||
{
|
||||
if (parent.closed) return false;
|
||||
parent.closed = true;
|
||||
waiting = parent.readerSource.isWaiting;
|
||||
|
||||
if (parent.items.Count == 0)
|
||||
{
|
||||
if (error == null)
|
||||
{
|
||||
if (parent.completedTaskSource != null)
|
||||
{
|
||||
parent.completedTaskSource.TrySetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.completedTask = UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent.completedTaskSource != null)
|
||||
{
|
||||
parent.completedTaskSource.TrySetException(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.completedTask = UniTask.FromException(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (waiting)
|
||||
{
|
||||
parent.readerSource.SingalCompleted(error);
|
||||
}
|
||||
}
|
||||
|
||||
parent.completionError = error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class SingleConsumerUnboundedChannelReader : ChannelReader<T>, IUniTaskSource<bool>
|
||||
{
|
||||
readonly Action<object> CancellationCallbackDelegate = CancellationCallback;
|
||||
readonly SingleConsumerUnboundedChannel<T> parent;
|
||||
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration cancellationTokenRegistration;
|
||||
UniTaskCompletionSourceCore<bool> core;
|
||||
internal bool isWaiting;
|
||||
|
||||
public SingleConsumerUnboundedChannelReader(SingleConsumerUnboundedChannel<T> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
|
||||
TaskTracker.TrackActiveTask(this, 4);
|
||||
}
|
||||
|
||||
public override UniTask Completion
|
||||
{
|
||||
get
|
||||
{
|
||||
if (parent.completedTaskSource != null) return parent.completedTaskSource.Task;
|
||||
|
||||
if (parent.closed)
|
||||
{
|
||||
return parent.completedTask;
|
||||
}
|
||||
|
||||
parent.completedTaskSource = new UniTaskCompletionSource();
|
||||
return parent.completedTaskSource.Task;
|
||||
}
|
||||
}
|
||||
|
||||
public override bool TryRead(out T item)
|
||||
{
|
||||
lock (parent.items)
|
||||
{
|
||||
if (parent.items.Count != 0)
|
||||
{
|
||||
item = parent.items.Dequeue();
|
||||
|
||||
// complete when all value was consumed.
|
||||
if (parent.closed && parent.items.Count == 0)
|
||||
{
|
||||
if (parent.completionError != null)
|
||||
{
|
||||
if (parent.completedTaskSource != null)
|
||||
{
|
||||
parent.completedTaskSource.TrySetException(parent.completionError);
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.completedTask = UniTask.FromException(parent.completionError);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (parent.completedTaskSource != null)
|
||||
{
|
||||
parent.completedTaskSource.TrySetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
parent.completedTask = UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
item = default;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override UniTask<bool> WaitToReadAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return UniTask.FromCanceled<bool>(cancellationToken);
|
||||
}
|
||||
|
||||
lock (parent.items)
|
||||
{
|
||||
if (parent.items.Count != 0)
|
||||
{
|
||||
return CompletedTasks.True;
|
||||
}
|
||||
|
||||
if (parent.closed)
|
||||
{
|
||||
if (parent.completionError == null)
|
||||
{
|
||||
return CompletedTasks.False;
|
||||
}
|
||||
else
|
||||
{
|
||||
return UniTask.FromException<bool>(parent.completionError);
|
||||
}
|
||||
}
|
||||
|
||||
cancellationTokenRegistration.Dispose();
|
||||
|
||||
core.Reset();
|
||||
isWaiting = true;
|
||||
|
||||
this.cancellationToken = cancellationToken;
|
||||
if (this.cancellationToken.CanBeCanceled)
|
||||
{
|
||||
cancellationTokenRegistration = this.cancellationToken.RegisterWithoutCaptureExecutionContext(CancellationCallbackDelegate, this);
|
||||
}
|
||||
|
||||
return new UniTask<bool>(this, core.Version);
|
||||
}
|
||||
}
|
||||
|
||||
public void SingalContinuation()
|
||||
{
|
||||
core.TrySetResult(true);
|
||||
}
|
||||
|
||||
public void SingalCancellation(CancellationToken cancellationToken)
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
|
||||
public void SingalCompleted(Exception error)
|
||||
{
|
||||
if (error != null)
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.TrySetException(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.TrySetResult(false);
|
||||
}
|
||||
}
|
||||
|
||||
public override IUniTaskAsyncEnumerable<T> ReadAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return new ReadAllAsyncEnumerable(this, cancellationToken);
|
||||
}
|
||||
|
||||
bool IUniTaskSource<bool>.GetResult(short token)
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
core.GetResult(token);
|
||||
}
|
||||
|
||||
UniTaskStatus IUniTaskSource.GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
void IUniTaskSource.OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
UniTaskStatus IUniTaskSource.UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
static void CancellationCallback(object state)
|
||||
{
|
||||
var self = (SingleConsumerUnboundedChannelReader)state;
|
||||
self.SingalCancellation(self.cancellationToken);
|
||||
}
|
||||
|
||||
sealed class ReadAllAsyncEnumerable : IUniTaskAsyncEnumerable<T>, IUniTaskAsyncEnumerator<T>
|
||||
{
|
||||
readonly Action<object> CancellationCallback1Delegate = CancellationCallback1;
|
||||
readonly Action<object> CancellationCallback2Delegate = CancellationCallback2;
|
||||
|
||||
readonly SingleConsumerUnboundedChannelReader parent;
|
||||
CancellationToken cancellationToken1;
|
||||
CancellationToken cancellationToken2;
|
||||
CancellationTokenRegistration cancellationTokenRegistration1;
|
||||
CancellationTokenRegistration cancellationTokenRegistration2;
|
||||
|
||||
T current;
|
||||
bool cacheValue;
|
||||
bool running;
|
||||
|
||||
public ReadAllAsyncEnumerable(SingleConsumerUnboundedChannelReader parent, CancellationToken cancellationToken)
|
||||
{
|
||||
this.parent = parent;
|
||||
this.cancellationToken1 = cancellationToken;
|
||||
}
|
||||
|
||||
public IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
throw new InvalidOperationException("Enumerator is already running, does not allow call GetAsyncEnumerator twice.");
|
||||
}
|
||||
|
||||
if (this.cancellationToken1 != cancellationToken)
|
||||
{
|
||||
this.cancellationToken2 = cancellationToken;
|
||||
}
|
||||
|
||||
if (this.cancellationToken1.CanBeCanceled)
|
||||
{
|
||||
this.cancellationTokenRegistration1 = this.cancellationToken1.RegisterWithoutCaptureExecutionContext(CancellationCallback1Delegate, this);
|
||||
}
|
||||
|
||||
if (this.cancellationToken2.CanBeCanceled)
|
||||
{
|
||||
this.cancellationTokenRegistration2 = this.cancellationToken2.RegisterWithoutCaptureExecutionContext(CancellationCallback2Delegate, this);
|
||||
}
|
||||
|
||||
running = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public T Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (cacheValue)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
parent.TryRead(out current);
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
public UniTask<bool> MoveNextAsync()
|
||||
{
|
||||
cacheValue = false;
|
||||
return parent.WaitToReadAsync(CancellationToken.None); // ok to use None, registered in ctor.
|
||||
}
|
||||
|
||||
public UniTask DisposeAsync()
|
||||
{
|
||||
cancellationTokenRegistration1.Dispose();
|
||||
cancellationTokenRegistration2.Dispose();
|
||||
return default;
|
||||
}
|
||||
|
||||
static void CancellationCallback1(object state)
|
||||
{
|
||||
var self = (ReadAllAsyncEnumerable)state;
|
||||
self.parent.SingalCancellation(self.cancellationToken1);
|
||||
}
|
||||
|
||||
static void CancellationCallback2(object state)
|
||||
{
|
||||
var self = (ReadAllAsyncEnumerable)state;
|
||||
self.parent.SingalCancellation(self.cancellationToken2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Runtime/Channel.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/Channel.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ceb3107bbdd1f14eb39091273798360
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime/CompilerServices.meta
Normal file
8
Assets/Plugins/UniTask/Runtime/CompilerServices.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 581d422ac5b39a647bfbb2d0c40176b0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS0436
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
internal sealed class AsyncMethodBuilderAttribute : Attribute
|
||||
{
|
||||
public Type BuilderType { get; }
|
||||
|
||||
public AsyncMethodBuilderAttribute(Type builderType)
|
||||
{
|
||||
BuilderType = builderType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 02ce354d37b10454e8376062f7cbe57a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,269 @@
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.CompilerServices
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct AsyncUniTaskMethodBuilder
|
||||
{
|
||||
IStateMachineRunnerPromise runnerPromise;
|
||||
Exception ex;
|
||||
|
||||
// 1. Static Create method.
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AsyncUniTaskMethodBuilder Create()
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// 2. TaskLike Task property.
|
||||
public UniTask Task
|
||||
{
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (runnerPromise != null)
|
||||
{
|
||||
return runnerPromise.Task;
|
||||
}
|
||||
else if (ex != null)
|
||||
{
|
||||
return UniTask.FromException(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. SetException
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
ex = exception;
|
||||
}
|
||||
else
|
||||
{
|
||||
runnerPromise.SetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. SetResult
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
if (runnerPromise != null)
|
||||
{
|
||||
runnerPromise.SetResult();
|
||||
}
|
||||
}
|
||||
|
||||
// 5. AwaitOnCompleted
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
AsyncUniTask<TStateMachine>.SetStateMachine(ref stateMachine, ref runnerPromise);
|
||||
}
|
||||
|
||||
awaiter.OnCompleted(runnerPromise.MoveNext);
|
||||
}
|
||||
|
||||
// 6. AwaitUnsafeOnCompleted
|
||||
[DebuggerHidden]
|
||||
[SecuritySafeCritical]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
AsyncUniTask<TStateMachine>.SetStateMachine(ref stateMachine, ref runnerPromise);
|
||||
}
|
||||
|
||||
awaiter.UnsafeOnCompleted(runnerPromise.MoveNext);
|
||||
}
|
||||
|
||||
// 7. Start
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// 8. SetStateMachine
|
||||
[DebuggerHidden]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
// don't use boxed stateMachine.
|
||||
}
|
||||
|
||||
#if DEBUG || !UNITY_2018_3_OR_NEWER
|
||||
// Important for IDE debugger.
|
||||
object debuggingId;
|
||||
private object ObjectIdForDebugger
|
||||
{
|
||||
get
|
||||
{
|
||||
if (debuggingId == null)
|
||||
{
|
||||
debuggingId = new object();
|
||||
}
|
||||
return debuggingId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct AsyncUniTaskMethodBuilder<T>
|
||||
{
|
||||
IStateMachineRunnerPromise<T> runnerPromise;
|
||||
Exception ex;
|
||||
T result;
|
||||
|
||||
// 1. Static Create method.
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AsyncUniTaskMethodBuilder<T> Create()
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// 2. TaskLike Task property.
|
||||
public UniTask<T> Task
|
||||
{
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
if (runnerPromise != null)
|
||||
{
|
||||
return runnerPromise.Task;
|
||||
}
|
||||
else if (ex != null)
|
||||
{
|
||||
return UniTask.FromException<T>(ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
return UniTask.FromResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. SetException
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
ex = exception;
|
||||
}
|
||||
else
|
||||
{
|
||||
runnerPromise.SetException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. SetResult
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
this.result = result;
|
||||
}
|
||||
else
|
||||
{
|
||||
runnerPromise.SetResult(result);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. AwaitOnCompleted
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
AsyncUniTask<TStateMachine, T>.SetStateMachine(ref stateMachine, ref runnerPromise);
|
||||
}
|
||||
|
||||
awaiter.OnCompleted(runnerPromise.MoveNext);
|
||||
}
|
||||
|
||||
// 6. AwaitUnsafeOnCompleted
|
||||
[DebuggerHidden]
|
||||
[SecuritySafeCritical]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (runnerPromise == null)
|
||||
{
|
||||
AsyncUniTask<TStateMachine, T>.SetStateMachine(ref stateMachine, ref runnerPromise);
|
||||
}
|
||||
|
||||
awaiter.UnsafeOnCompleted(runnerPromise.MoveNext);
|
||||
}
|
||||
|
||||
// 7. Start
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// 8. SetStateMachine
|
||||
[DebuggerHidden]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
// don't use boxed stateMachine.
|
||||
}
|
||||
|
||||
#if DEBUG || !UNITY_2018_3_OR_NEWER
|
||||
// Important for IDE debugger.
|
||||
object debuggingId;
|
||||
private object ObjectIdForDebugger
|
||||
{
|
||||
get
|
||||
{
|
||||
if (debuggingId == null)
|
||||
{
|
||||
debuggingId = new object();
|
||||
}
|
||||
return debuggingId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68d72a45afdec574ebc26e7de2c38330
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,137 @@
|
||||
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.CompilerServices
|
||||
{
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public struct AsyncUniTaskVoidMethodBuilder
|
||||
{
|
||||
IStateMachineRunner runner;
|
||||
|
||||
// 1. Static Create method.
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static AsyncUniTaskVoidMethodBuilder Create()
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
// 2. TaskLike Task property(void)
|
||||
public UniTaskVoid Task
|
||||
{
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. SetException
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
// runner is finished, return first.
|
||||
if (runner != null)
|
||||
{
|
||||
#if ENABLE_IL2CPP
|
||||
// workaround for IL2CPP bug.
|
||||
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, runner.ReturnAction);
|
||||
#else
|
||||
runner.Return();
|
||||
#endif
|
||||
runner = null;
|
||||
}
|
||||
|
||||
UniTaskScheduler.PublishUnobservedTaskException(exception);
|
||||
}
|
||||
|
||||
// 4. SetResult
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetResult()
|
||||
{
|
||||
// runner is finished, return.
|
||||
if (runner != null)
|
||||
{
|
||||
#if ENABLE_IL2CPP
|
||||
// workaround for IL2CPP bug.
|
||||
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, runner.ReturnAction);
|
||||
#else
|
||||
runner.Return();
|
||||
#endif
|
||||
runner = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. AwaitOnCompleted
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : INotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (runner == null)
|
||||
{
|
||||
AsyncUniTaskVoid<TStateMachine>.SetStateMachine(ref stateMachine, ref runner);
|
||||
}
|
||||
|
||||
awaiter.OnCompleted(runner.MoveNext);
|
||||
}
|
||||
|
||||
// 6. AwaitUnsafeOnCompleted
|
||||
[DebuggerHidden]
|
||||
[SecuritySafeCritical]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AwaitUnsafeOnCompleted<TAwaiter, TStateMachine>(ref TAwaiter awaiter, ref TStateMachine stateMachine)
|
||||
where TAwaiter : ICriticalNotifyCompletion
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
if (runner == null)
|
||||
{
|
||||
AsyncUniTaskVoid<TStateMachine>.SetStateMachine(ref stateMachine, ref runner);
|
||||
}
|
||||
|
||||
awaiter.UnsafeOnCompleted(runner.MoveNext);
|
||||
}
|
||||
|
||||
// 7. Start
|
||||
[DebuggerHidden]
|
||||
public void Start<TStateMachine>(ref TStateMachine stateMachine)
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// 8. SetStateMachine
|
||||
[DebuggerHidden]
|
||||
public void SetStateMachine(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
// don't use boxed stateMachine.
|
||||
}
|
||||
|
||||
#if DEBUG || !UNITY_2018_3_OR_NEWER
|
||||
// Important for IDE debugger.
|
||||
object debuggingId;
|
||||
private object ObjectIdForDebugger
|
||||
{
|
||||
get
|
||||
{
|
||||
if (debuggingId == null)
|
||||
{
|
||||
debuggingId = new object();
|
||||
}
|
||||
return debuggingId;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e891aaac17b933a47a9d7fa3b8e1226f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,380 @@
|
||||
#pragma warning disable CS1591
|
||||
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.CompilerServices
|
||||
{
|
||||
// #ENABLE_IL2CPP in this file is to avoid bug of IL2CPP VM.
|
||||
// Issue is tracked on https://issuetracker.unity3d.com/issues/il2cpp-incorrect-results-when-calling-a-method-from-outside-class-in-a-struct
|
||||
// but currently it is labeled `Won't Fix`.
|
||||
|
||||
internal interface IStateMachineRunner
|
||||
{
|
||||
Action MoveNext { get; }
|
||||
void Return();
|
||||
|
||||
#if ENABLE_IL2CPP
|
||||
Action ReturnAction { get; }
|
||||
#endif
|
||||
}
|
||||
|
||||
internal interface IStateMachineRunnerPromise : IUniTaskSource
|
||||
{
|
||||
Action MoveNext { get; }
|
||||
UniTask Task { get; }
|
||||
void SetResult();
|
||||
void SetException(Exception exception);
|
||||
}
|
||||
|
||||
internal interface IStateMachineRunnerPromise<T> : IUniTaskSource<T>
|
||||
{
|
||||
Action MoveNext { get; }
|
||||
UniTask<T> Task { get; }
|
||||
void SetResult(T result);
|
||||
void SetException(Exception exception);
|
||||
}
|
||||
|
||||
internal static class StateMachineUtility
|
||||
{
|
||||
// Get AsyncStateMachine internal state to check IL2CPP bug
|
||||
public static int GetState(IAsyncStateMachine stateMachine)
|
||||
{
|
||||
var info = stateMachine.GetType().GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
|
||||
.First(x => x.Name.EndsWith("__state"));
|
||||
return (int)info.GetValue(stateMachine);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AsyncUniTaskVoid<TStateMachine> : IStateMachineRunner, ITaskPoolNode<AsyncUniTaskVoid<TStateMachine>>, IUniTaskSource
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
static TaskPool<AsyncUniTaskVoid<TStateMachine>> pool;
|
||||
|
||||
#if ENABLE_IL2CPP
|
||||
public Action ReturnAction { get; }
|
||||
#endif
|
||||
|
||||
TStateMachine stateMachine;
|
||||
|
||||
public Action MoveNext { get; }
|
||||
|
||||
public AsyncUniTaskVoid()
|
||||
{
|
||||
MoveNext = Run;
|
||||
#if ENABLE_IL2CPP
|
||||
ReturnAction = Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunner runnerFieldRef)
|
||||
{
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new AsyncUniTaskVoid<TStateMachine>();
|
||||
}
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
runnerFieldRef = result; // set runner before copied.
|
||||
result.stateMachine = stateMachine; // copy struct StateMachine(in release build).
|
||||
}
|
||||
|
||||
static AsyncUniTaskVoid()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(AsyncUniTaskVoid<TStateMachine>), () => pool.Size);
|
||||
}
|
||||
|
||||
AsyncUniTaskVoid<TStateMachine> nextNode;
|
||||
public ref AsyncUniTaskVoid<TStateMachine> NextNode => ref nextNode;
|
||||
|
||||
public void Return()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
stateMachine = default;
|
||||
pool.TryPush(this);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void Run()
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
// dummy interface implementation for TaskTracker.
|
||||
|
||||
UniTaskStatus IUniTaskSource.GetStatus(short token)
|
||||
{
|
||||
return UniTaskStatus.Pending;
|
||||
}
|
||||
|
||||
UniTaskStatus IUniTaskSource.UnsafeGetStatus()
|
||||
{
|
||||
return UniTaskStatus.Pending;
|
||||
}
|
||||
|
||||
void IUniTaskSource.OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AsyncUniTask<TStateMachine> : IStateMachineRunnerPromise, IUniTaskSource, ITaskPoolNode<AsyncUniTask<TStateMachine>>
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
static TaskPool<AsyncUniTask<TStateMachine>> pool;
|
||||
|
||||
#if ENABLE_IL2CPP
|
||||
readonly Action returnDelegate;
|
||||
#endif
|
||||
public Action MoveNext { get; }
|
||||
|
||||
TStateMachine stateMachine;
|
||||
UniTaskCompletionSourceCore<AsyncUnit> core;
|
||||
|
||||
AsyncUniTask()
|
||||
{
|
||||
MoveNext = Run;
|
||||
#if ENABLE_IL2CPP
|
||||
returnDelegate = Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunnerPromise runnerPromiseFieldRef)
|
||||
{
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new AsyncUniTask<TStateMachine>();
|
||||
}
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
runnerPromiseFieldRef = result; // set runner before copied.
|
||||
result.stateMachine = stateMachine; // copy struct StateMachine(in release build).
|
||||
}
|
||||
|
||||
AsyncUniTask<TStateMachine> nextNode;
|
||||
public ref AsyncUniTask<TStateMachine> NextNode => ref nextNode;
|
||||
|
||||
static AsyncUniTask()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(AsyncUniTask<TStateMachine>), () => pool.Size);
|
||||
}
|
||||
|
||||
void Return()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
stateMachine = default;
|
||||
pool.TryPush(this);
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
stateMachine = default;
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void Run()
|
||||
{
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
public UniTask Task
|
||||
{
|
||||
[DebuggerHidden]
|
||||
get
|
||||
{
|
||||
return new UniTask(this, core.Version);
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void SetResult()
|
||||
{
|
||||
core.TrySetResult(AsyncUnit.Default);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
core.TrySetException(exception);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if ENABLE_IL2CPP
|
||||
// workaround for IL2CPP bug.
|
||||
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, returnDelegate);
|
||||
#else
|
||||
TryReturn();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AsyncUniTask<TStateMachine, T> : IStateMachineRunnerPromise<T>, IUniTaskSource<T>, ITaskPoolNode<AsyncUniTask<TStateMachine, T>>
|
||||
where TStateMachine : IAsyncStateMachine
|
||||
{
|
||||
static TaskPool<AsyncUniTask<TStateMachine, T>> pool;
|
||||
|
||||
#if ENABLE_IL2CPP
|
||||
readonly Action returnDelegate;
|
||||
#endif
|
||||
|
||||
public Action MoveNext { get; }
|
||||
|
||||
TStateMachine stateMachine;
|
||||
UniTaskCompletionSourceCore<T> core;
|
||||
|
||||
AsyncUniTask()
|
||||
{
|
||||
MoveNext = Run;
|
||||
#if ENABLE_IL2CPP
|
||||
returnDelegate = Return;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void SetStateMachine(ref TStateMachine stateMachine, ref IStateMachineRunnerPromise<T> runnerPromiseFieldRef)
|
||||
{
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new AsyncUniTask<TStateMachine, T>();
|
||||
}
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
runnerPromiseFieldRef = result; // set runner before copied.
|
||||
result.stateMachine = stateMachine; // copy struct StateMachine(in release build).
|
||||
}
|
||||
|
||||
AsyncUniTask<TStateMachine, T> nextNode;
|
||||
public ref AsyncUniTask<TStateMachine, T> NextNode => ref nextNode;
|
||||
|
||||
static AsyncUniTask()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(AsyncUniTask<TStateMachine, T>), () => pool.Size);
|
||||
}
|
||||
|
||||
void Return()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
stateMachine = default;
|
||||
pool.TryPush(this);
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
stateMachine = default;
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void Run()
|
||||
{
|
||||
// UnityEngine.Debug.Log($"MoveNext State:" + StateMachineUtility.GetState(stateMachine));
|
||||
stateMachine.MoveNext();
|
||||
}
|
||||
|
||||
public UniTask<T> Task
|
||||
{
|
||||
[DebuggerHidden]
|
||||
get
|
||||
{
|
||||
return new UniTask<T>(this, core.Version);
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void SetResult(T result)
|
||||
{
|
||||
core.TrySetResult(result);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void SetException(Exception exception)
|
||||
{
|
||||
core.TrySetException(exception);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public T GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if ENABLE_IL2CPP
|
||||
// workaround for IL2CPP bug.
|
||||
PlayerLoopHelper.AddContinuation(PlayerLoopTiming.LastPostLateUpdate, returnDelegate);
|
||||
#else
|
||||
TryReturn();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
GetResult(token);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98649642833cabf44a9dc060ce4c84a1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
34
Assets/Plugins/UniTask/Runtime/EnumerableAsyncExtensions.cs
Normal file
34
Assets/Plugins/UniTask/Runtime/EnumerableAsyncExtensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static class EnumerableAsyncExtensions
|
||||
{
|
||||
// overload resolver - .Select(async x => { }) : IEnumerable<UniTask<T>>
|
||||
|
||||
public static IEnumerable<UniTask> Select<T>(this IEnumerable<T> source, Func<T, UniTask> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
|
||||
public static IEnumerable<UniTask<TR>> Select<T, TR>(this IEnumerable<T> source, Func<T, UniTask<TR>> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
|
||||
public static IEnumerable<UniTask> Select<T>(this IEnumerable<T> source, Func<T, int, UniTask> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
|
||||
public static IEnumerable<UniTask<TR>> Select<T, TR>(this IEnumerable<T> source, Func<T, int, UniTask<TR>> selector)
|
||||
{
|
||||
return System.Linq.Enumerable.Select(source, selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff50260d74bd54c4b92cf99895549445
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
287
Assets/Plugins/UniTask/Runtime/EnumeratorAsyncExtensions.cs
Normal file
287
Assets/Plugins/UniTask/Runtime/EnumeratorAsyncExtensions.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Reflection;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static class EnumeratorAsyncExtensions
|
||||
{
|
||||
public static UniTask.Awaiter GetAwaiter<T>(this T enumerator)
|
||||
where T : IEnumerator
|
||||
{
|
||||
var e = (IEnumerator)enumerator;
|
||||
Error.ThrowArgumentNullException(e, nameof(enumerator));
|
||||
return new UniTask(EnumeratorPromise.Create(e, PlayerLoopTiming.Update, CancellationToken.None, out var token), token).GetAwaiter();
|
||||
}
|
||||
|
||||
public static UniTask WithCancellation(this IEnumerator enumerator, CancellationToken cancellationToken)
|
||||
{
|
||||
Error.ThrowArgumentNullException(enumerator, nameof(enumerator));
|
||||
return new UniTask(EnumeratorPromise.Create(enumerator, PlayerLoopTiming.Update, cancellationToken, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask ToUniTask(this IEnumerator enumerator, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken))
|
||||
{
|
||||
Error.ThrowArgumentNullException(enumerator, nameof(enumerator));
|
||||
return new UniTask(EnumeratorPromise.Create(enumerator, timing, cancellationToken, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask ToUniTask(this IEnumerator enumerator, MonoBehaviour coroutineRunner)
|
||||
{
|
||||
var source = AutoResetUniTaskCompletionSource.Create();
|
||||
coroutineRunner.StartCoroutine(Core(enumerator, coroutineRunner, source));
|
||||
return source.Task;
|
||||
}
|
||||
|
||||
static IEnumerator Core(IEnumerator inner, MonoBehaviour coroutineRunner, AutoResetUniTaskCompletionSource source)
|
||||
{
|
||||
yield return coroutineRunner.StartCoroutine(inner);
|
||||
source.TrySetResult();
|
||||
}
|
||||
|
||||
sealed class EnumeratorPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<EnumeratorPromise>
|
||||
{
|
||||
static TaskPool<EnumeratorPromise> pool;
|
||||
EnumeratorPromise nextNode;
|
||||
public ref EnumeratorPromise NextNode => ref nextNode;
|
||||
|
||||
static EnumeratorPromise()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(EnumeratorPromise), () => pool.Size);
|
||||
}
|
||||
|
||||
IEnumerator innerEnumerator;
|
||||
CancellationToken cancellationToken;
|
||||
int initialFrame;
|
||||
bool loopRunning;
|
||||
bool calledGetResult;
|
||||
|
||||
UniTaskCompletionSourceCore<object> core;
|
||||
|
||||
EnumeratorPromise()
|
||||
{
|
||||
}
|
||||
|
||||
public static IUniTaskSource Create(IEnumerator innerEnumerator, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
|
||||
}
|
||||
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new EnumeratorPromise();
|
||||
}
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
result.innerEnumerator = ConsumeEnumerator(innerEnumerator);
|
||||
result.cancellationToken = cancellationToken;
|
||||
result.loopRunning = true;
|
||||
result.calledGetResult = false;
|
||||
result.initialFrame = -1;
|
||||
|
||||
token = result.core.Version;
|
||||
|
||||
// run immediately.
|
||||
if (result.MoveNext())
|
||||
{
|
||||
PlayerLoopHelper.AddAction(timing, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
calledGetResult = true;
|
||||
core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!loopRunning)
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (calledGetResult)
|
||||
{
|
||||
loopRunning = false;
|
||||
TryReturn();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (innerEnumerator == null) // invalid status, returned but loop running?
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
loopRunning = false;
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (initialFrame == -1)
|
||||
{
|
||||
// Time can not touch in threadpool.
|
||||
if (PlayerLoopHelper.IsMainThread)
|
||||
{
|
||||
initialFrame = Time.frameCount;
|
||||
}
|
||||
}
|
||||
else if (initialFrame == Time.frameCount)
|
||||
{
|
||||
return true; // already executed in first frame, skip.
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (innerEnumerator.MoveNext())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loopRunning = false;
|
||||
core.TrySetException(ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
loopRunning = false;
|
||||
core.TrySetResult(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
innerEnumerator = default;
|
||||
cancellationToken = default;
|
||||
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
|
||||
// Unwrap YieldInstructions
|
||||
|
||||
static IEnumerator ConsumeEnumerator(IEnumerator enumerator)
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var current = enumerator.Current;
|
||||
if (current == null)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
else if (current is CustomYieldInstruction cyi)
|
||||
{
|
||||
// WWW, WaitForSecondsRealtime
|
||||
while (cyi.keepWaiting)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else if (current is YieldInstruction)
|
||||
{
|
||||
IEnumerator innerCoroutine = null;
|
||||
switch (current)
|
||||
{
|
||||
case AsyncOperation ao:
|
||||
innerCoroutine = UnwrapWaitAsyncOperation(ao);
|
||||
break;
|
||||
case WaitForSeconds wfs:
|
||||
innerCoroutine = UnwrapWaitForSeconds(wfs);
|
||||
break;
|
||||
}
|
||||
if (innerCoroutine != null)
|
||||
{
|
||||
while (innerCoroutine.MoveNext())
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
goto WARN;
|
||||
}
|
||||
}
|
||||
else if (current is IEnumerator e3)
|
||||
{
|
||||
var e4 = ConsumeEnumerator(e3);
|
||||
while (e4.MoveNext())
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
goto WARN;
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
WARN:
|
||||
// WaitForEndOfFrame, WaitForFixedUpdate, others.
|
||||
UnityEngine.Debug.LogWarning($"yield {current.GetType().Name} is not supported on await IEnumerator or IEnumerator.ToUniTask(), please use ToUniTask(MonoBehaviour coroutineRunner) instead.");
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
static readonly FieldInfo waitForSeconds_Seconds = typeof(WaitForSeconds).GetField("m_Seconds", BindingFlags.Instance | BindingFlags.GetField | BindingFlags.NonPublic);
|
||||
|
||||
static IEnumerator UnwrapWaitForSeconds(WaitForSeconds waitForSeconds)
|
||||
{
|
||||
var second = (float)waitForSeconds_Seconds.GetValue(waitForSeconds);
|
||||
var elapsed = 0.0f;
|
||||
while (true)
|
||||
{
|
||||
yield return null;
|
||||
|
||||
elapsed += Time.deltaTime;
|
||||
if (elapsed >= second)
|
||||
{
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static IEnumerator UnwrapWaitAsyncOperation(AsyncOperation asyncOperation)
|
||||
{
|
||||
while (!asyncOperation.isDone)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bc661232f11e4a741af54ba1c175d5ee
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/Plugins/UniTask/Runtime/ExceptionExtensions.cs
Normal file
14
Assets/Plugins/UniTask/Runtime/ExceptionExtensions.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
|
||||
using System;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static class ExceptionExtensions
|
||||
{
|
||||
public static bool IsOperationCanceledException(this Exception exception)
|
||||
{
|
||||
return exception is OperationCanceledException;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Plugins/UniTask/Runtime/ExceptionExtensions.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/ExceptionExtensions.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 930800098504c0d46958ce23a0495202
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime/External.meta
Normal file
8
Assets/Plugins/UniTask/Runtime/External.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8cd864258e6ae924e9c4ed9ea88862a4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime/External/Addressables.meta
vendored
Normal file
8
Assets/Plugins/UniTask/Runtime/External/Addressables.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d382cb4ff5b2f9e448dd19c088ede33e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
483
Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs
vendored
Normal file
483
Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs
vendored
Normal file
@@ -0,0 +1,483 @@
|
||||
// asmdef Version Defines, enabled when com.unity.addressables is imported.
|
||||
|
||||
#if UNITASK_ADDRESSABLE_SUPPORT
|
||||
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static class AddressablesAsyncExtensions
|
||||
{
|
||||
#region AsyncOperationHandle
|
||||
|
||||
public static UniTask.Awaiter GetAwaiter(this AsyncOperationHandle handle)
|
||||
{
|
||||
return ToUniTask(handle).GetAwaiter();
|
||||
}
|
||||
|
||||
public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken, bool cancelImmediately = false, bool autoReleaseWhenCanceled = false)
|
||||
{
|
||||
return ToUniTask(handle, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately, autoReleaseWhenCanceled: autoReleaseWhenCanceled);
|
||||
}
|
||||
|
||||
public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false, bool autoReleaseWhenCanceled = false)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken);
|
||||
|
||||
if (!handle.IsValid())
|
||||
{
|
||||
// autoReleaseHandle:true handle is invalid(immediately internal handle == null) so return completed.
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
if (handle.IsDone)
|
||||
{
|
||||
if (handle.Status == AsyncOperationStatus.Failed)
|
||||
{
|
||||
return UniTask.FromException(handle.OperationException);
|
||||
}
|
||||
return UniTask.CompletedTask;
|
||||
}
|
||||
|
||||
return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, timing, progress, cancellationToken, cancelImmediately, autoReleaseWhenCanceled, out var token), token);
|
||||
}
|
||||
|
||||
public struct AsyncOperationHandleAwaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
AsyncOperationHandle handle;
|
||||
Action<AsyncOperationHandle> continuationAction;
|
||||
|
||||
public AsyncOperationHandleAwaiter(AsyncOperationHandle handle)
|
||||
{
|
||||
this.handle = handle;
|
||||
this.continuationAction = null;
|
||||
}
|
||||
|
||||
public bool IsCompleted => handle.IsDone;
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
if (continuationAction != null)
|
||||
{
|
||||
handle.Completed -= continuationAction;
|
||||
continuationAction = null;
|
||||
}
|
||||
|
||||
if (handle.Status == AsyncOperationStatus.Failed)
|
||||
{
|
||||
var e = handle.OperationException;
|
||||
handle = default;
|
||||
ExceptionDispatchInfo.Capture(e).Throw();
|
||||
}
|
||||
|
||||
var result = handle.Result;
|
||||
handle = default;
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(Action continuation)
|
||||
{
|
||||
Error.ThrowWhenContinuationIsAlreadyRegistered(continuationAction);
|
||||
continuationAction = PooledDelegate<AsyncOperationHandle>.Create(continuation);
|
||||
handle.Completed += continuationAction;
|
||||
}
|
||||
}
|
||||
|
||||
sealed class AsyncOperationHandleConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource>
|
||||
{
|
||||
static TaskPool<AsyncOperationHandleConfiguredSource> pool;
|
||||
AsyncOperationHandleConfiguredSource nextNode;
|
||||
public ref AsyncOperationHandleConfiguredSource NextNode => ref nextNode;
|
||||
|
||||
static AsyncOperationHandleConfiguredSource()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleConfiguredSource), () => pool.Size);
|
||||
}
|
||||
|
||||
readonly Action<AsyncOperationHandle> completedCallback;
|
||||
AsyncOperationHandle handle;
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration cancellationTokenRegistration;
|
||||
IProgress<float> progress;
|
||||
bool autoReleaseWhenCanceled;
|
||||
bool cancelImmediately;
|
||||
bool completed;
|
||||
|
||||
UniTaskCompletionSourceCore<AsyncUnit> core;
|
||||
|
||||
AsyncOperationHandleConfiguredSource()
|
||||
{
|
||||
completedCallback = HandleCompleted;
|
||||
}
|
||||
|
||||
public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, bool autoReleaseWhenCanceled, out short token)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
|
||||
}
|
||||
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new AsyncOperationHandleConfiguredSource();
|
||||
}
|
||||
|
||||
result.handle = handle;
|
||||
result.progress = progress;
|
||||
result.cancellationToken = cancellationToken;
|
||||
result.cancelImmediately = cancelImmediately;
|
||||
result.autoReleaseWhenCanceled = autoReleaseWhenCanceled;
|
||||
result.completed = false;
|
||||
|
||||
if (cancelImmediately && cancellationToken.CanBeCanceled)
|
||||
{
|
||||
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
|
||||
{
|
||||
var promise = (AsyncOperationHandleConfiguredSource)state;
|
||||
if (promise.autoReleaseWhenCanceled && promise.handle.IsValid())
|
||||
{
|
||||
Addressables.Release(promise.handle);
|
||||
}
|
||||
promise.core.TrySetCanceled(promise.cancellationToken);
|
||||
}, result);
|
||||
}
|
||||
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
PlayerLoopHelper.AddAction(timing, result);
|
||||
|
||||
handle.Completed += result.completedCallback;
|
||||
|
||||
token = result.core.Version;
|
||||
return result;
|
||||
}
|
||||
|
||||
void HandleCompleted(AsyncOperationHandle _)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
handle.Completed -= completedCallback;
|
||||
}
|
||||
|
||||
if (completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
completed = true;
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (autoReleaseWhenCanceled && handle.IsValid())
|
||||
{
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
else if (handle.Status == AsyncOperationStatus.Failed)
|
||||
{
|
||||
core.TrySetException(handle.OperationException);
|
||||
}
|
||||
else
|
||||
{
|
||||
core.TrySetResult(AsyncUnit.Default);
|
||||
}
|
||||
}
|
||||
|
||||
public void GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
completed = true;
|
||||
if (autoReleaseWhenCanceled && handle.IsValid())
|
||||
{
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progress != null && handle.IsValid())
|
||||
{
|
||||
progress.Report(handle.GetDownloadStatus().Percent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
handle = default;
|
||||
progress = default;
|
||||
cancellationToken = default;
|
||||
cancellationTokenRegistration.Dispose();
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region AsyncOperationHandle_T
|
||||
|
||||
public static UniTask<T>.Awaiter GetAwaiter<T>(this AsyncOperationHandle<T> handle)
|
||||
{
|
||||
return ToUniTask(handle).GetAwaiter();
|
||||
}
|
||||
|
||||
public static UniTask<T> WithCancellation<T>(this AsyncOperationHandle<T> handle, CancellationToken cancellationToken, bool cancelImmediately = false, bool autoReleaseWhenCanceled = false)
|
||||
{
|
||||
return ToUniTask(handle, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately, autoReleaseWhenCanceled: autoReleaseWhenCanceled);
|
||||
}
|
||||
|
||||
public static UniTask<T> ToUniTask<T>(this AsyncOperationHandle<T> handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false, bool autoReleaseWhenCanceled = false)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<T>(cancellationToken);
|
||||
|
||||
if (!handle.IsValid())
|
||||
{
|
||||
throw new Exception("Attempting to use an invalid operation handle");
|
||||
}
|
||||
|
||||
if (handle.IsDone)
|
||||
{
|
||||
if (handle.Status == AsyncOperationStatus.Failed)
|
||||
{
|
||||
return UniTask.FromException<T>(handle.OperationException);
|
||||
}
|
||||
return UniTask.FromResult(handle.Result);
|
||||
}
|
||||
|
||||
return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, timing, progress, cancellationToken, cancelImmediately, autoReleaseWhenCanceled, out var token), token);
|
||||
}
|
||||
|
||||
sealed class AsyncOperationHandleConfiguredSource<T> : IUniTaskSource<T>, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource<T>>
|
||||
{
|
||||
static TaskPool<AsyncOperationHandleConfiguredSource<T>> pool;
|
||||
AsyncOperationHandleConfiguredSource<T> nextNode;
|
||||
public ref AsyncOperationHandleConfiguredSource<T> NextNode => ref nextNode;
|
||||
|
||||
static AsyncOperationHandleConfiguredSource()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(AsyncOperationHandleConfiguredSource<T>), () => pool.Size);
|
||||
}
|
||||
|
||||
readonly Action<AsyncOperationHandle<T>> completedCallback;
|
||||
AsyncOperationHandle<T> handle;
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration cancellationTokenRegistration;
|
||||
IProgress<float> progress;
|
||||
bool autoReleaseWhenCanceled;
|
||||
bool cancelImmediately;
|
||||
bool completed;
|
||||
|
||||
UniTaskCompletionSourceCore<T> core;
|
||||
|
||||
AsyncOperationHandleConfiguredSource()
|
||||
{
|
||||
completedCallback = HandleCompleted;
|
||||
}
|
||||
|
||||
public static IUniTaskSource<T> Create(AsyncOperationHandle<T> handle, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, bool autoReleaseWhenCanceled, out short token)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return AutoResetUniTaskCompletionSource<T>.CreateFromCanceled(cancellationToken, out token);
|
||||
}
|
||||
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new AsyncOperationHandleConfiguredSource<T>();
|
||||
}
|
||||
|
||||
result.handle = handle;
|
||||
result.cancellationToken = cancellationToken;
|
||||
result.completed = false;
|
||||
result.progress = progress;
|
||||
result.autoReleaseWhenCanceled = autoReleaseWhenCanceled;
|
||||
result.cancelImmediately = cancelImmediately;
|
||||
|
||||
if (cancelImmediately && cancellationToken.CanBeCanceled)
|
||||
{
|
||||
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
|
||||
{
|
||||
var promise = (AsyncOperationHandleConfiguredSource<T>)state;
|
||||
if (promise.autoReleaseWhenCanceled && promise.handle.IsValid())
|
||||
{
|
||||
Addressables.Release(promise.handle);
|
||||
}
|
||||
promise.core.TrySetCanceled(promise.cancellationToken);
|
||||
}, result);
|
||||
}
|
||||
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
PlayerLoopHelper.AddAction(timing, result);
|
||||
|
||||
handle.Completed += result.completedCallback;
|
||||
|
||||
token = result.core.Version;
|
||||
return result;
|
||||
}
|
||||
|
||||
void HandleCompleted(AsyncOperationHandle<T> argHandle)
|
||||
{
|
||||
if (handle.IsValid())
|
||||
{
|
||||
handle.Completed -= completedCallback;
|
||||
}
|
||||
|
||||
if (completed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
completed = true;
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (autoReleaseWhenCanceled && handle.IsValid())
|
||||
{
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
else if (argHandle.Status == AsyncOperationStatus.Failed)
|
||||
{
|
||||
core.TrySetException(argHandle.OperationException);
|
||||
}
|
||||
else
|
||||
{
|
||||
core.TrySetResult(argHandle.Result);
|
||||
}
|
||||
}
|
||||
|
||||
public T GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
return core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!(cancelImmediately && cancellationToken.IsCancellationRequested))
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
else
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IUniTaskSource.GetResult(short token)
|
||||
{
|
||||
GetResult(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (completed)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
completed = true;
|
||||
if (autoReleaseWhenCanceled && handle.IsValid())
|
||||
{
|
||||
Addressables.Release(handle);
|
||||
}
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (progress != null && handle.IsValid())
|
||||
{
|
||||
progress.Report(handle.GetDownloadStatus().Percent);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
handle = default;
|
||||
progress = default;
|
||||
cancellationToken = default;
|
||||
cancellationTokenRegistration.Dispose();
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs.meta
vendored
Normal file
11
Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3dc6441f9094f354b931dc3c79fb99e5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
28
Assets/Plugins/UniTask/Runtime/External/Addressables/UniTask.Addressables.asmdef
vendored
Normal file
28
Assets/Plugins/UniTask/Runtime/External/Addressables/UniTask.Addressables.asmdef
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "UniTask.Addressables",
|
||||
"references": [
|
||||
"UniTask",
|
||||
"Unity.ResourceManager",
|
||||
"Unity.Addressables"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.addressables",
|
||||
"expression": "",
|
||||
"define": "UNITASK_ADDRESSABLE_SUPPORT"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.addressables.cn",
|
||||
"expression": "",
|
||||
"define": "UNITASK_ADDRESSABLE_SUPPORT"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Plugins/UniTask/Runtime/External/Addressables/UniTask.Addressables.asmdef.meta
vendored
Normal file
7
Assets/Plugins/UniTask/Runtime/External/Addressables/UniTask.Addressables.asmdef.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 593a5b492d29ac6448b1ebf7f035ef33
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime/External/DOTween.meta
vendored
Normal file
8
Assets/Plugins/UniTask/Runtime/External/DOTween.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09c71e13672501741a58879125e90bd2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
436
Assets/Plugins/UniTask/Runtime/External/DOTween/DOTweenAsyncExtensions.cs
vendored
Normal file
436
Assets/Plugins/UniTask/Runtime/External/DOTween/DOTweenAsyncExtensions.cs
vendored
Normal file
@@ -0,0 +1,436 @@
|
||||
// asmdef Version Defines, enabled when com.demigiant.dotween is imported.
|
||||
|
||||
#if UNITASK_DOTWEEN_SUPPORT
|
||||
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
using DG.Tweening;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public enum TweenCancelBehaviour
|
||||
{
|
||||
Kill,
|
||||
KillWithCompleteCallback,
|
||||
Complete,
|
||||
CompleteWithSequenceCallback,
|
||||
CancelAwait,
|
||||
|
||||
// AndCancelAwait
|
||||
KillAndCancelAwait,
|
||||
KillWithCompleteCallbackAndCancelAwait,
|
||||
CompleteAndCancelAwait,
|
||||
CompleteWithSequenceCallbackAndCancelAwait
|
||||
}
|
||||
|
||||
public static class DOTweenAsyncExtensions
|
||||
{
|
||||
enum CallbackType
|
||||
{
|
||||
Kill,
|
||||
Complete,
|
||||
Pause,
|
||||
Play,
|
||||
Rewind,
|
||||
StepComplete
|
||||
}
|
||||
|
||||
public static TweenAwaiter GetAwaiter(this Tween tween)
|
||||
{
|
||||
return new TweenAwaiter(tween);
|
||||
}
|
||||
|
||||
public static UniTask WithCancellation(this Tween tween, CancellationToken cancellationToken)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, TweenCancelBehaviour.Kill, cancellationToken, CallbackType.Kill, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask ToUniTask(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.Kill, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask AwaitForComplete(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.Complete, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask AwaitForPause(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.Pause, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask AwaitForPlay(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.Play, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask AwaitForRewind(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.Rewind, out var token), token);
|
||||
}
|
||||
|
||||
public static UniTask AwaitForStepComplete(this Tween tween, TweenCancelBehaviour tweenCancelBehaviour = TweenCancelBehaviour.Kill, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Error.ThrowArgumentNullException(tween, nameof(tween));
|
||||
|
||||
if (!tween.IsActive()) return UniTask.CompletedTask;
|
||||
return new UniTask(TweenConfiguredSource.Create(tween, tweenCancelBehaviour, cancellationToken, CallbackType.StepComplete, out var token), token);
|
||||
}
|
||||
|
||||
public struct TweenAwaiter : ICriticalNotifyCompletion
|
||||
{
|
||||
readonly Tween tween;
|
||||
|
||||
// killed(non active) as completed.
|
||||
public bool IsCompleted => !tween.IsActive();
|
||||
|
||||
public TweenAwaiter(Tween tween)
|
||||
{
|
||||
this.tween = tween;
|
||||
}
|
||||
|
||||
public TweenAwaiter GetAwaiter()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
}
|
||||
|
||||
public void OnCompleted(System.Action continuation)
|
||||
{
|
||||
UnsafeOnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void UnsafeOnCompleted(System.Action continuation)
|
||||
{
|
||||
// onKill is called after OnCompleted, both Complete(false/true) and Kill(false/true).
|
||||
tween.onKill = PooledTweenCallback.Create(continuation);
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TweenConfiguredSource : IUniTaskSource, ITaskPoolNode<TweenConfiguredSource>
|
||||
{
|
||||
static TaskPool<TweenConfiguredSource> pool;
|
||||
TweenConfiguredSource nextNode;
|
||||
public ref TweenConfiguredSource NextNode => ref nextNode;
|
||||
|
||||
static TweenConfiguredSource()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(TweenConfiguredSource), () => pool.Size);
|
||||
}
|
||||
|
||||
readonly TweenCallback onCompleteCallbackDelegate;
|
||||
|
||||
Tween tween;
|
||||
TweenCancelBehaviour cancelBehaviour;
|
||||
CancellationToken cancellationToken;
|
||||
CancellationTokenRegistration cancellationRegistration;
|
||||
CallbackType callbackType;
|
||||
bool canceled;
|
||||
|
||||
TweenCallback originalCompleteAction;
|
||||
UniTaskCompletionSourceCore<AsyncUnit> core;
|
||||
|
||||
TweenConfiguredSource()
|
||||
{
|
||||
onCompleteCallbackDelegate = OnCompleteCallbackDelegate;
|
||||
}
|
||||
|
||||
public static IUniTaskSource Create(Tween tween, TweenCancelBehaviour cancelBehaviour, CancellationToken cancellationToken, CallbackType callbackType, out short token)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
DoCancelBeforeCreate(tween, cancelBehaviour);
|
||||
return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token);
|
||||
}
|
||||
|
||||
if (!pool.TryPop(out var result))
|
||||
{
|
||||
result = new TweenConfiguredSource();
|
||||
}
|
||||
|
||||
result.tween = tween;
|
||||
result.cancelBehaviour = cancelBehaviour;
|
||||
result.cancellationToken = cancellationToken;
|
||||
result.callbackType = callbackType;
|
||||
result.canceled = false;
|
||||
|
||||
switch (callbackType)
|
||||
{
|
||||
case CallbackType.Kill:
|
||||
result.originalCompleteAction = tween.onKill;
|
||||
tween.onKill = result.onCompleteCallbackDelegate;
|
||||
break;
|
||||
case CallbackType.Complete:
|
||||
result.originalCompleteAction = tween.onComplete;
|
||||
tween.onComplete = result.onCompleteCallbackDelegate;
|
||||
break;
|
||||
case CallbackType.Pause:
|
||||
result.originalCompleteAction = tween.onPause;
|
||||
tween.onPause = result.onCompleteCallbackDelegate;
|
||||
break;
|
||||
case CallbackType.Play:
|
||||
result.originalCompleteAction = tween.onPlay;
|
||||
tween.onPlay = result.onCompleteCallbackDelegate;
|
||||
break;
|
||||
case CallbackType.Rewind:
|
||||
result.originalCompleteAction = tween.onRewind;
|
||||
tween.onRewind = result.onCompleteCallbackDelegate;
|
||||
break;
|
||||
case CallbackType.StepComplete:
|
||||
result.originalCompleteAction = tween.onStepComplete;
|
||||
tween.onStepComplete = result.onCompleteCallbackDelegate;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.originalCompleteAction == result.onCompleteCallbackDelegate)
|
||||
{
|
||||
result.originalCompleteAction = null;
|
||||
}
|
||||
|
||||
if (cancellationToken.CanBeCanceled)
|
||||
{
|
||||
result.cancellationRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(x =>
|
||||
{
|
||||
var source = (TweenConfiguredSource)x;
|
||||
switch (source.cancelBehaviour)
|
||||
{
|
||||
case TweenCancelBehaviour.Kill:
|
||||
default:
|
||||
source.tween.Kill(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.KillAndCancelAwait:
|
||||
source.canceled = true;
|
||||
source.tween.Kill(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.KillWithCompleteCallback:
|
||||
source.tween.Kill(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.KillWithCompleteCallbackAndCancelAwait:
|
||||
source.canceled = true;
|
||||
source.tween.Kill(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.Complete:
|
||||
source.tween.Complete(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.CompleteAndCancelAwait:
|
||||
source.canceled = true;
|
||||
source.tween.Complete(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.CompleteWithSequenceCallback:
|
||||
source.tween.Complete(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.CompleteWithSequenceCallbackAndCancelAwait:
|
||||
source.canceled = true;
|
||||
source.tween.Complete(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.CancelAwait:
|
||||
source.RestoreOriginalCallback();
|
||||
source.core.TrySetCanceled(source.cancellationToken);
|
||||
break;
|
||||
}
|
||||
}, result);
|
||||
}
|
||||
|
||||
TaskTracker.TrackActiveTask(result, 3);
|
||||
|
||||
token = result.core.Version;
|
||||
return result;
|
||||
}
|
||||
|
||||
void OnCompleteCallbackDelegate()
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
if (this.cancelBehaviour == TweenCancelBehaviour.KillAndCancelAwait
|
||||
|| this.cancelBehaviour == TweenCancelBehaviour.KillWithCompleteCallbackAndCancelAwait
|
||||
|| this.cancelBehaviour == TweenCancelBehaviour.CompleteAndCancelAwait
|
||||
|| this.cancelBehaviour == TweenCancelBehaviour.CompleteWithSequenceCallbackAndCancelAwait
|
||||
|| this.cancelBehaviour == TweenCancelBehaviour.CancelAwait)
|
||||
{
|
||||
canceled = true;
|
||||
}
|
||||
}
|
||||
if (canceled)
|
||||
{
|
||||
core.TrySetCanceled(cancellationToken);
|
||||
}
|
||||
else
|
||||
{
|
||||
originalCompleteAction?.Invoke();
|
||||
core.TrySetResult(AsyncUnit.Default);
|
||||
}
|
||||
}
|
||||
|
||||
static void DoCancelBeforeCreate(Tween tween, TweenCancelBehaviour tweenCancelBehaviour)
|
||||
{
|
||||
|
||||
switch (tweenCancelBehaviour)
|
||||
{
|
||||
case TweenCancelBehaviour.Kill:
|
||||
default:
|
||||
tween.Kill(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.KillAndCancelAwait:
|
||||
tween.Kill(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.KillWithCompleteCallback:
|
||||
tween.Kill(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.KillWithCompleteCallbackAndCancelAwait:
|
||||
tween.Kill(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.Complete:
|
||||
tween.Complete(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.CompleteAndCancelAwait:
|
||||
tween.Complete(false);
|
||||
break;
|
||||
case TweenCancelBehaviour.CompleteWithSequenceCallback:
|
||||
tween.Complete(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.CompleteWithSequenceCallbackAndCancelAwait:
|
||||
tween.Complete(true);
|
||||
break;
|
||||
case TweenCancelBehaviour.CancelAwait:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void GetResult(short token)
|
||||
{
|
||||
try
|
||||
{
|
||||
core.GetResult(token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
TryReturn();
|
||||
}
|
||||
}
|
||||
|
||||
public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return core.GetStatus(token);
|
||||
}
|
||||
|
||||
public UniTaskStatus UnsafeGetStatus()
|
||||
{
|
||||
return core.UnsafeGetStatus();
|
||||
}
|
||||
|
||||
public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
core.OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
bool TryReturn()
|
||||
{
|
||||
TaskTracker.RemoveTracking(this);
|
||||
core.Reset();
|
||||
cancellationRegistration.Dispose();
|
||||
|
||||
RestoreOriginalCallback();
|
||||
|
||||
tween = default;
|
||||
cancellationToken = default;
|
||||
originalCompleteAction = default;
|
||||
return pool.TryPush(this);
|
||||
}
|
||||
|
||||
void RestoreOriginalCallback()
|
||||
{
|
||||
switch (callbackType)
|
||||
{
|
||||
case CallbackType.Kill:
|
||||
tween.onKill = originalCompleteAction;
|
||||
break;
|
||||
case CallbackType.Complete:
|
||||
tween.onComplete = originalCompleteAction;
|
||||
break;
|
||||
case CallbackType.Pause:
|
||||
tween.onPause = originalCompleteAction;
|
||||
break;
|
||||
case CallbackType.Play:
|
||||
tween.onPlay = originalCompleteAction;
|
||||
break;
|
||||
case CallbackType.Rewind:
|
||||
tween.onRewind = originalCompleteAction;
|
||||
break;
|
||||
case CallbackType.StepComplete:
|
||||
tween.onStepComplete = originalCompleteAction;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed class PooledTweenCallback
|
||||
{
|
||||
static readonly ConcurrentQueue<PooledTweenCallback> pool = new ConcurrentQueue<PooledTweenCallback>();
|
||||
|
||||
readonly TweenCallback runDelegate;
|
||||
|
||||
Action continuation;
|
||||
|
||||
|
||||
PooledTweenCallback()
|
||||
{
|
||||
runDelegate = Run;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static TweenCallback Create(Action continuation)
|
||||
{
|
||||
if (!pool.TryDequeue(out var item))
|
||||
{
|
||||
item = new PooledTweenCallback();
|
||||
}
|
||||
|
||||
item.continuation = continuation;
|
||||
return item.runDelegate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void Run()
|
||||
{
|
||||
var call = continuation;
|
||||
continuation = null;
|
||||
if (call != null)
|
||||
{
|
||||
pool.Enqueue(this);
|
||||
call.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Plugins/UniTask/Runtime/External/DOTween/DOTweenAsyncExtensions.cs.meta
vendored
Normal file
11
Assets/Plugins/UniTask/Runtime/External/DOTween/DOTweenAsyncExtensions.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1f448d5bc5b232e4f98d89d5d1832e8e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/Plugins/UniTask/Runtime/External/DOTween/UniTask.DOTween.asmdef
vendored
Normal file
22
Assets/Plugins/UniTask/Runtime/External/DOTween/UniTask.DOTween.asmdef
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "UniTask.DOTween",
|
||||
"references": [
|
||||
"UniTask",
|
||||
"DOTween.Modules"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.demigiant.dotween",
|
||||
"expression": "",
|
||||
"define": "UNITASK_DOTWEEN_SUPPORT"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Plugins/UniTask/Runtime/External/DOTween/UniTask.DOTween.asmdef.meta
vendored
Normal file
7
Assets/Plugins/UniTask/Runtime/External/DOTween/UniTask.DOTween.asmdef.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 029c1c1b674aaae47a6841a0b89ad80e
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime/External/TextMeshPro.meta
vendored
Normal file
8
Assets/Plugins/UniTask/Runtime/External/TextMeshPro.meta
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45a53097709caf946ae2938a5ef0c04d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
224
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/TextMeshProAsyncExtensions.InputField.cs
vendored
Normal file
224
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/TextMeshProAsyncExtensions.InputField.cs
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
#if UNITASK_TEXTMESHPRO_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using TMPro;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static partial class TextMeshProAsyncExtensions
|
||||
{
|
||||
public static IAsyncValueChangedEventHandler<string> GetAsyncValueChangedEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onValueChanged, inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncValueChangedEventHandler<string> GetAsyncValueChangedEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onValueChanged, cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<string> OnValueChangedAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onValueChanged, inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<string> OnValueChangedAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onValueChanged, cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnValueChangedAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onValueChanged, inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnValueChangedAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onValueChanged, cancellationToken);
|
||||
}
|
||||
|
||||
public static IAsyncEndEditEventHandler<string> GetAsyncEndEditEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onEndEdit, inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncEndEditEventHandler<string> GetAsyncEndEditEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onEndEdit, cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<string> OnEndEditAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onEndEdit, inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<string> OnEndEditAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onEndEdit, cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnEndEditAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onEndEdit, inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnEndEditAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onEndEdit, cancellationToken);
|
||||
}
|
||||
|
||||
public static IAsyncEndTextSelectionEventHandler<(string, int, int)> GetAsyncEndTextSelectionEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onEndTextSelection), inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncEndTextSelectionEventHandler<(string, int, int)> GetAsyncEndTextSelectionEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onEndTextSelection), cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<(string, int, int)> OnEndTextSelectionAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onEndTextSelection), inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<(string, int, int)> OnEndTextSelectionAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onEndTextSelection), cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<(string, int, int)> OnEndTextSelectionAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<(string, int, int)>(new TextSelectionEventConverter(inputField.onEndTextSelection), inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<(string, int, int)> OnEndTextSelectionAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<(string, int, int)>(new TextSelectionEventConverter(inputField.onEndTextSelection), cancellationToken);
|
||||
}
|
||||
|
||||
public static IAsyncTextSelectionEventHandler<(string, int, int)> GetAsyncTextSelectionEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onTextSelection), inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncTextSelectionEventHandler<(string, int, int)> GetAsyncTextSelectionEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onTextSelection), cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<(string, int, int)> OnTextSelectionAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onTextSelection), inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<(string, int, int)> OnTextSelectionAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<(string, int, int)>(new TextSelectionEventConverter(inputField.onTextSelection), cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<(string, int, int)> OnTextSelectionAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<(string, int, int)>(new TextSelectionEventConverter(inputField.onTextSelection), inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<(string, int, int)> OnTextSelectionAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<(string, int, int)>(new TextSelectionEventConverter(inputField.onTextSelection), cancellationToken);
|
||||
}
|
||||
|
||||
public static IAsyncDeselectEventHandler<string> GetAsyncDeselectEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onDeselect, inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncDeselectEventHandler<string> GetAsyncDeselectEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onDeselect, cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<string> OnDeselectAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onDeselect, inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<string> OnDeselectAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onDeselect, cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnDeselectAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onDeselect, inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnDeselectAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onDeselect, cancellationToken);
|
||||
}
|
||||
|
||||
public static IAsyncSelectEventHandler<string> GetAsyncSelectEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSelect, inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncSelectEventHandler<string> GetAsyncSelectEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSelect, cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<string> OnSelectAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSelect, inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<string> OnSelectAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSelect, cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnSelectAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onSelect, inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnSelectAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onSelect, cancellationToken);
|
||||
}
|
||||
|
||||
public static IAsyncSubmitEventHandler<string> GetAsyncSubmitEventHandler(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSubmit, inputField.GetCancellationTokenOnDestroy(), false);
|
||||
}
|
||||
|
||||
public static IAsyncSubmitEventHandler<string> GetAsyncSubmitEventHandler(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSubmit, cancellationToken, false);
|
||||
}
|
||||
|
||||
public static UniTask<string> OnSubmitAsync(this TMP_InputField inputField)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSubmit, inputField.GetCancellationTokenOnDestroy(), true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static UniTask<string> OnSubmitAsync(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new AsyncUnityEventHandler<string>(inputField.onSubmit, cancellationToken, true).OnInvokeAsync();
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnSubmitAsAsyncEnumerable(this TMP_InputField inputField)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onSubmit, inputField.GetCancellationTokenOnDestroy());
|
||||
}
|
||||
|
||||
public static IUniTaskAsyncEnumerable<string> OnSubmitAsAsyncEnumerable(this TMP_InputField inputField, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UnityEventHandlerAsyncEnumerable<string>(inputField.onSubmit, cancellationToken);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 79f4f2475e0b2c44e97ed1dee760627b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
130
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/TextMeshProAsyncExtensions.cs
vendored
Normal file
130
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/TextMeshProAsyncExtensions.cs
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
#if UNITASK_TEXTMESHPRO_SUPPORT
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using TMPro;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public static partial class TextMeshProAsyncExtensions
|
||||
{
|
||||
// <string> -> Text
|
||||
public static void BindTo(this IUniTaskAsyncEnumerable<string> source, TMP_Text text, bool rebindOnError = true)
|
||||
{
|
||||
BindToCore(source, text, text.GetCancellationTokenOnDestroy(), rebindOnError).Forget();
|
||||
}
|
||||
|
||||
public static void BindTo(this IUniTaskAsyncEnumerable<string> source, TMP_Text text, CancellationToken cancellationToken, bool rebindOnError = true)
|
||||
{
|
||||
BindToCore(source, text, cancellationToken, rebindOnError).Forget();
|
||||
}
|
||||
|
||||
static async UniTaskVoid BindToCore(IUniTaskAsyncEnumerable<string> source, TMP_Text text, CancellationToken cancellationToken, bool rebindOnError)
|
||||
{
|
||||
var repeat = false;
|
||||
BIND_AGAIN:
|
||||
var e = source.GetAsyncEnumerator(cancellationToken);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
bool moveNext;
|
||||
try
|
||||
{
|
||||
moveNext = await e.MoveNextAsync();
|
||||
repeat = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is OperationCanceledException) return;
|
||||
|
||||
if (rebindOnError && !repeat)
|
||||
{
|
||||
repeat = true;
|
||||
goto BIND_AGAIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (!moveNext) return;
|
||||
|
||||
text.text = e.Current;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (e != null)
|
||||
{
|
||||
await e.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// <T> -> Text
|
||||
|
||||
public static void BindTo<T>(this IUniTaskAsyncEnumerable<T> source, TMP_Text text, bool rebindOnError = true)
|
||||
{
|
||||
BindToCore(source, text, text.GetCancellationTokenOnDestroy(), rebindOnError).Forget();
|
||||
}
|
||||
|
||||
public static void BindTo<T>(this IUniTaskAsyncEnumerable<T> source, TMP_Text text, CancellationToken cancellationToken, bool rebindOnError = true)
|
||||
{
|
||||
BindToCore(source, text, cancellationToken, rebindOnError).Forget();
|
||||
}
|
||||
|
||||
public static void BindTo<T>(this AsyncReactiveProperty<T> source, TMP_Text text, bool rebindOnError = true)
|
||||
{
|
||||
BindToCore(source, text, text.GetCancellationTokenOnDestroy(), rebindOnError).Forget();
|
||||
}
|
||||
|
||||
static async UniTaskVoid BindToCore<T>(IUniTaskAsyncEnumerable<T> source, TMP_Text text, CancellationToken cancellationToken, bool rebindOnError)
|
||||
{
|
||||
var repeat = false;
|
||||
BIND_AGAIN:
|
||||
var e = source.GetAsyncEnumerator(cancellationToken);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
bool moveNext;
|
||||
try
|
||||
{
|
||||
moveNext = await e.MoveNextAsync();
|
||||
repeat = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex is OperationCanceledException) return;
|
||||
|
||||
if (rebindOnError && !repeat)
|
||||
{
|
||||
repeat = true;
|
||||
goto BIND_AGAIN;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
if (!moveNext) return;
|
||||
|
||||
text.text = e.Current.ToString();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (e != null)
|
||||
{
|
||||
await e.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
11
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/TextMeshProAsyncExtensions.cs.meta
vendored
Normal file
11
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/TextMeshProAsyncExtensions.cs.meta
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b6ba480edafb67d4e91bb10feb64fae5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
27
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/UniTask.TextMeshPro.asmdef
vendored
Normal file
27
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/UniTask.TextMeshPro.asmdef
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "UniTask.TextMeshPro",
|
||||
"references": [
|
||||
"UniTask",
|
||||
"Unity.TextMeshPro"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.textmeshpro",
|
||||
"expression": "",
|
||||
"define": "UNITASK_TEXTMESHPRO_SUPPORT"
|
||||
},
|
||||
{
|
||||
"name": "com.unity.ugui",
|
||||
"expression": "2.0.0",
|
||||
"define": "UNITASK_TEXTMESHPRO_SUPPORT"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
7
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/UniTask.TextMeshPro.asmdef.meta
vendored
Normal file
7
Assets/Plugins/UniTask/Runtime/External/TextMeshPro/UniTask.TextMeshPro.asmdef.meta
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dc47925d1a5fa2946bdd37746b2b5d48
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
91
Assets/Plugins/UniTask/Runtime/IUniTaskAsyncEnumerable.cs
Normal file
91
Assets/Plugins/UniTask/Runtime/IUniTaskAsyncEnumerable.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public interface IUniTaskAsyncEnumerable<out T>
|
||||
{
|
||||
IUniTaskAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
public interface IUniTaskAsyncEnumerator<out T> : IUniTaskAsyncDisposable
|
||||
{
|
||||
T Current { get; }
|
||||
UniTask<bool> MoveNextAsync();
|
||||
}
|
||||
|
||||
public interface IUniTaskAsyncDisposable
|
||||
{
|
||||
UniTask DisposeAsync();
|
||||
}
|
||||
|
||||
public interface IUniTaskOrderedAsyncEnumerable<TElement> : IUniTaskAsyncEnumerable<TElement>
|
||||
{
|
||||
IUniTaskOrderedAsyncEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, TKey> keySelector, IComparer<TKey> comparer, bool descending);
|
||||
IUniTaskOrderedAsyncEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, UniTask<TKey>> keySelector, IComparer<TKey> comparer, bool descending);
|
||||
IUniTaskOrderedAsyncEnumerable<TElement> CreateOrderedEnumerable<TKey>(Func<TElement, CancellationToken, UniTask<TKey>> keySelector, IComparer<TKey> comparer, bool descending);
|
||||
}
|
||||
|
||||
public interface IConnectableUniTaskAsyncEnumerable<out T> : IUniTaskAsyncEnumerable<T>
|
||||
{
|
||||
IDisposable Connect();
|
||||
}
|
||||
|
||||
// don't use AsyncGrouping.
|
||||
//public interface IUniTaskAsyncGrouping<out TKey, out TElement> : IUniTaskAsyncEnumerable<TElement>
|
||||
//{
|
||||
// TKey Key { get; }
|
||||
//}
|
||||
|
||||
public static class UniTaskAsyncEnumerableExtensions
|
||||
{
|
||||
public static UniTaskCancelableAsyncEnumerable<T> WithCancellation<T>(this IUniTaskAsyncEnumerable<T> source, CancellationToken cancellationToken)
|
||||
{
|
||||
return new UniTaskCancelableAsyncEnumerable<T>(source, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly struct UniTaskCancelableAsyncEnumerable<T>
|
||||
{
|
||||
private readonly IUniTaskAsyncEnumerable<T> enumerable;
|
||||
private readonly CancellationToken cancellationToken;
|
||||
|
||||
internal UniTaskCancelableAsyncEnumerable(IUniTaskAsyncEnumerable<T> enumerable, CancellationToken cancellationToken)
|
||||
{
|
||||
this.enumerable = enumerable;
|
||||
this.cancellationToken = cancellationToken;
|
||||
}
|
||||
|
||||
public Enumerator GetAsyncEnumerator()
|
||||
{
|
||||
return new Enumerator(enumerable.GetAsyncEnumerator(cancellationToken));
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Auto)]
|
||||
public readonly struct Enumerator
|
||||
{
|
||||
private readonly IUniTaskAsyncEnumerator<T> enumerator;
|
||||
|
||||
internal Enumerator(IUniTaskAsyncEnumerator<T> enumerator)
|
||||
{
|
||||
this.enumerator = enumerator;
|
||||
}
|
||||
|
||||
public T Current => enumerator.Current;
|
||||
|
||||
public UniTask<bool> MoveNextAsync()
|
||||
{
|
||||
return enumerator.MoveNextAsync();
|
||||
}
|
||||
|
||||
|
||||
public UniTask DisposeAsync()
|
||||
{
|
||||
return enumerator.DisposeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b20cf9f02ac585948a4372fa4ee06504
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
127
Assets/Plugins/UniTask/Runtime/IUniTaskSource.cs
Normal file
127
Assets/Plugins/UniTask/Runtime/IUniTaskSource.cs
Normal file
@@ -0,0 +1,127 @@
|
||||
#pragma warning disable CS1591
|
||||
#pragma warning disable CS0108
|
||||
|
||||
#if (UNITASK_NETCORE && !NETSTANDARD2_0) || UNITY_2022_3_OR_NEWER
|
||||
#define SUPPORT_VALUETASK
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
public enum UniTaskStatus
|
||||
{
|
||||
/// <summary>The operation has not yet completed.</summary>
|
||||
Pending = 0,
|
||||
/// <summary>The operation completed successfully.</summary>
|
||||
Succeeded = 1,
|
||||
/// <summary>The operation completed with an error.</summary>
|
||||
Faulted = 2,
|
||||
/// <summary>The operation completed due to cancellation.</summary>
|
||||
Canceled = 3
|
||||
}
|
||||
|
||||
// similar as IValueTaskSource
|
||||
public interface IUniTaskSource
|
||||
#if SUPPORT_VALUETASK
|
||||
: System.Threading.Tasks.Sources.IValueTaskSource
|
||||
#endif
|
||||
{
|
||||
UniTaskStatus GetStatus(short token);
|
||||
void OnCompleted(Action<object> continuation, object state, short token);
|
||||
void GetResult(short token);
|
||||
|
||||
UniTaskStatus UnsafeGetStatus(); // only for debug use.
|
||||
|
||||
#if SUPPORT_VALUETASK
|
||||
|
||||
System.Threading.Tasks.Sources.ValueTaskSourceStatus System.Threading.Tasks.Sources.IValueTaskSource.GetStatus(short token)
|
||||
{
|
||||
return (System.Threading.Tasks.Sources.ValueTaskSourceStatus)(int)((IUniTaskSource)this).GetStatus(token);
|
||||
}
|
||||
|
||||
void System.Threading.Tasks.Sources.IValueTaskSource.GetResult(short token)
|
||||
{
|
||||
((IUniTaskSource)this).GetResult(token);
|
||||
}
|
||||
|
||||
void System.Threading.Tasks.Sources.IValueTaskSource.OnCompleted(Action<object> continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags)
|
||||
{
|
||||
// ignore flags, always none.
|
||||
((IUniTaskSource)this).OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
public interface IUniTaskSource<out T> : IUniTaskSource
|
||||
#if SUPPORT_VALUETASK
|
||||
, System.Threading.Tasks.Sources.IValueTaskSource<T>
|
||||
#endif
|
||||
{
|
||||
new T GetResult(short token);
|
||||
|
||||
#if SUPPORT_VALUETASK
|
||||
|
||||
new public UniTaskStatus GetStatus(short token)
|
||||
{
|
||||
return ((IUniTaskSource)this).GetStatus(token);
|
||||
}
|
||||
|
||||
new public void OnCompleted(Action<object> continuation, object state, short token)
|
||||
{
|
||||
((IUniTaskSource)this).OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
System.Threading.Tasks.Sources.ValueTaskSourceStatus System.Threading.Tasks.Sources.IValueTaskSource<T>.GetStatus(short token)
|
||||
{
|
||||
return (System.Threading.Tasks.Sources.ValueTaskSourceStatus)(int)((IUniTaskSource)this).GetStatus(token);
|
||||
}
|
||||
|
||||
T System.Threading.Tasks.Sources.IValueTaskSource<T>.GetResult(short token)
|
||||
{
|
||||
return ((IUniTaskSource<T>)this).GetResult(token);
|
||||
}
|
||||
|
||||
void System.Threading.Tasks.Sources.IValueTaskSource<T>.OnCompleted(Action<object> continuation, object state, short token, System.Threading.Tasks.Sources.ValueTaskSourceOnCompletedFlags flags)
|
||||
{
|
||||
// ignore flags, always none.
|
||||
((IUniTaskSource)this).OnCompleted(continuation, state, token);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
public static class UniTaskStatusExtensions
|
||||
{
|
||||
/// <summary>status != Pending.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCompleted(this UniTaskStatus status)
|
||||
{
|
||||
return status != UniTaskStatus.Pending;
|
||||
}
|
||||
|
||||
/// <summary>status == Succeeded.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCompletedSuccessfully(this UniTaskStatus status)
|
||||
{
|
||||
return status == UniTaskStatus.Succeeded;
|
||||
}
|
||||
|
||||
/// <summary>status == Canceled.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsCanceled(this UniTaskStatus status)
|
||||
{
|
||||
return status == UniTaskStatus.Canceled;
|
||||
}
|
||||
|
||||
/// <summary>status == Faulted.</summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static bool IsFaulted(this UniTaskStatus status)
|
||||
{
|
||||
return status == UniTaskStatus.Faulted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
Assets/Plugins/UniTask/Runtime/IUniTaskSource.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/IUniTaskSource.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3e4d023d8404ab742b5e808c98097c3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/Plugins/UniTask/Runtime/Internal.meta
Normal file
8
Assets/Plugins/UniTask/Runtime/Internal.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cefa04af66d98a543b6fe498e9658ed1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
150
Assets/Plugins/UniTask/Runtime/Internal/ArrayPool.cs
Normal file
150
Assets/Plugins/UniTask/Runtime/Internal/ArrayPool.cs
Normal file
@@ -0,0 +1,150 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
// Same interface as System.Buffers.ArrayPool<T> but only provides Shared.
|
||||
|
||||
internal sealed class ArrayPool<T>
|
||||
{
|
||||
// Same size as System.Buffers.DefaultArrayPool<T>
|
||||
const int DefaultMaxNumberOfArraysPerBucket = 50;
|
||||
|
||||
static readonly T[] EmptyArray = new T[0];
|
||||
|
||||
public static readonly ArrayPool<T> Shared = new ArrayPool<T>();
|
||||
|
||||
readonly MinimumQueue<T[]>[] buckets;
|
||||
readonly SpinLock[] locks;
|
||||
|
||||
ArrayPool()
|
||||
{
|
||||
// see: GetQueueIndex
|
||||
buckets = new MinimumQueue<T[]>[18];
|
||||
locks = new SpinLock[18];
|
||||
for (int i = 0; i < buckets.Length; i++)
|
||||
{
|
||||
buckets[i] = new MinimumQueue<T[]>(4);
|
||||
locks[i] = new SpinLock(false);
|
||||
}
|
||||
}
|
||||
|
||||
public T[] Rent(int minimumLength)
|
||||
{
|
||||
if (minimumLength < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException("minimumLength");
|
||||
}
|
||||
else if (minimumLength == 0)
|
||||
{
|
||||
return EmptyArray;
|
||||
}
|
||||
|
||||
var size = CalculateSize(minimumLength);
|
||||
var index = GetQueueIndex(size);
|
||||
if (index != -1)
|
||||
{
|
||||
var q = buckets[index];
|
||||
var lockTaken = false;
|
||||
try
|
||||
{
|
||||
locks[index].Enter(ref lockTaken);
|
||||
|
||||
if (q.Count != 0)
|
||||
{
|
||||
return q.Dequeue();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) locks[index].Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
return new T[size];
|
||||
}
|
||||
|
||||
public void Return(T[] array, bool clearArray = false)
|
||||
{
|
||||
if (array == null || array.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var index = GetQueueIndex(array.Length);
|
||||
if (index != -1)
|
||||
{
|
||||
if (clearArray)
|
||||
{
|
||||
Array.Clear(array, 0, array.Length);
|
||||
}
|
||||
|
||||
var q = buckets[index];
|
||||
var lockTaken = false;
|
||||
|
||||
try
|
||||
{
|
||||
locks[index].Enter(ref lockTaken);
|
||||
|
||||
if (q.Count > DefaultMaxNumberOfArraysPerBucket)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
q.Enqueue(array);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) locks[index].Exit(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int CalculateSize(int size)
|
||||
{
|
||||
size--;
|
||||
size |= size >> 1;
|
||||
size |= size >> 2;
|
||||
size |= size >> 4;
|
||||
size |= size >> 8;
|
||||
size |= size >> 16;
|
||||
size += 1;
|
||||
|
||||
if (size < 8)
|
||||
{
|
||||
size = 8;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static int GetQueueIndex(int size)
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case 8: return 0;
|
||||
case 16: return 1;
|
||||
case 32: return 2;
|
||||
case 64: return 3;
|
||||
case 128: return 4;
|
||||
case 256: return 5;
|
||||
case 512: return 6;
|
||||
case 1024: return 7;
|
||||
case 2048: return 8;
|
||||
case 4096: return 9;
|
||||
case 8192: return 10;
|
||||
case 16384: return 11;
|
||||
case 32768: return 12;
|
||||
case 65536: return 13;
|
||||
case 131072: return 14;
|
||||
case 262144: return 15;
|
||||
case 524288: return 16;
|
||||
case 1048576: return 17; // max array length
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Assets/Plugins/UniTask/Runtime/Internal/ArrayPool.cs.meta
Normal file
12
Assets/Plugins/UniTask/Runtime/Internal/ArrayPool.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f83ebad81fb89fb4882331616ca6d248
|
||||
timeCreated: 1532361008
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
115
Assets/Plugins/UniTask/Runtime/Internal/ArrayPoolUtil.cs
Normal file
115
Assets/Plugins/UniTask/Runtime/Internal/ArrayPoolUtil.cs
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal static class ArrayPoolUtil
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal static void EnsureCapacity<T>(ref T[] array, int index, ArrayPool<T> pool)
|
||||
{
|
||||
if (array.Length <= index)
|
||||
{
|
||||
EnsureCapacityCore(ref array, index, pool);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void EnsureCapacityCore<T>(ref T[] array, int index, ArrayPool<T> pool)
|
||||
{
|
||||
if (array.Length <= index)
|
||||
{
|
||||
var newSize = array.Length * 2;
|
||||
var newArray = pool.Rent((index < newSize) ? newSize : (index * 2));
|
||||
Array.Copy(array, 0, newArray, 0, array.Length);
|
||||
|
||||
pool.Return(array, clearArray: !RuntimeHelpersAbstraction.IsWellKnownNoReferenceContainsType<T>());
|
||||
|
||||
array = newArray;
|
||||
}
|
||||
}
|
||||
|
||||
public static RentArray<T> Materialize<T>(IEnumerable<T> source)
|
||||
{
|
||||
if (source is T[] array)
|
||||
{
|
||||
return new RentArray<T>(array, array.Length, null);
|
||||
}
|
||||
|
||||
var defaultCount = 32;
|
||||
if (source is ICollection<T> coll)
|
||||
{
|
||||
if (coll.Count == 0)
|
||||
{
|
||||
return new RentArray<T>(Array.Empty<T>(), 0, null);
|
||||
}
|
||||
|
||||
defaultCount = coll.Count;
|
||||
var pool = ArrayPool<T>.Shared;
|
||||
var buffer = pool.Rent(defaultCount);
|
||||
coll.CopyTo(buffer, 0);
|
||||
return new RentArray<T>(buffer, coll.Count, pool);
|
||||
}
|
||||
else if (source is IReadOnlyCollection<T> rcoll)
|
||||
{
|
||||
defaultCount = rcoll.Count;
|
||||
}
|
||||
|
||||
if (defaultCount == 0)
|
||||
{
|
||||
return new RentArray<T>(Array.Empty<T>(), 0, null);
|
||||
}
|
||||
|
||||
{
|
||||
var pool = ArrayPool<T>.Shared;
|
||||
|
||||
var index = 0;
|
||||
var buffer = pool.Rent(defaultCount);
|
||||
foreach (var item in source)
|
||||
{
|
||||
EnsureCapacity(ref buffer, index, pool);
|
||||
buffer[index++] = item;
|
||||
}
|
||||
|
||||
return new RentArray<T>(buffer, index, pool);
|
||||
}
|
||||
}
|
||||
|
||||
public struct RentArray<T> : IDisposable
|
||||
{
|
||||
public readonly T[] Array;
|
||||
public readonly int Length;
|
||||
ArrayPool<T> pool;
|
||||
|
||||
public RentArray(T[] array, int length, ArrayPool<T> pool)
|
||||
{
|
||||
this.Array = array;
|
||||
this.Length = length;
|
||||
this.pool = pool;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DisposeManually(!RuntimeHelpersAbstraction.IsWellKnownNoReferenceContainsType<T>());
|
||||
}
|
||||
|
||||
public void DisposeManually(bool clearArray)
|
||||
{
|
||||
if (pool != null)
|
||||
{
|
||||
if (clearArray)
|
||||
{
|
||||
System.Array.Clear(Array, 0, Length);
|
||||
}
|
||||
|
||||
pool.Return(Array, clearArray: false);
|
||||
pool = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 424cc208fb61d4e448b08fcfa0eee25e
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
73
Assets/Plugins/UniTask/Runtime/Internal/ArrayUtil.cs
Normal file
73
Assets/Plugins/UniTask/Runtime/Internal/ArrayUtil.cs
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal static class ArrayUtil
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void EnsureCapacity<T>(ref T[] array, int index)
|
||||
{
|
||||
if (array.Length <= index)
|
||||
{
|
||||
EnsureCore(ref array, index);
|
||||
}
|
||||
}
|
||||
|
||||
// rare case, no inlining.
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void EnsureCore<T>(ref T[] array, int index)
|
||||
{
|
||||
var newSize = array.Length * 2;
|
||||
var newArray = new T[(index < newSize) ? newSize : (index * 2)];
|
||||
Array.Copy(array, 0, newArray, 0, array.Length);
|
||||
|
||||
array = newArray;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Optimizing utility to avoid .ToArray() that creates buffer copy(cut to just size).
|
||||
/// </summary>
|
||||
public static (T[] array, int length) Materialize<T>(IEnumerable<T> source)
|
||||
{
|
||||
if (source is T[] array)
|
||||
{
|
||||
return (array, array.Length);
|
||||
}
|
||||
|
||||
var defaultCount = 4;
|
||||
if (source is ICollection<T> coll)
|
||||
{
|
||||
defaultCount = coll.Count;
|
||||
var buffer = new T[defaultCount];
|
||||
coll.CopyTo(buffer, 0);
|
||||
return (buffer, defaultCount);
|
||||
}
|
||||
else if (source is IReadOnlyCollection<T> rcoll)
|
||||
{
|
||||
defaultCount = rcoll.Count;
|
||||
}
|
||||
|
||||
if (defaultCount == 0)
|
||||
{
|
||||
return (Array.Empty<T>(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
var index = 0;
|
||||
var buffer = new T[defaultCount];
|
||||
foreach (var item in source)
|
||||
{
|
||||
EnsureCapacity(ref buffer, index);
|
||||
buffer[index++] = item;
|
||||
}
|
||||
|
||||
return (buffer, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Assets/Plugins/UniTask/Runtime/Internal/ArrayUtil.cs.meta
Normal file
12
Assets/Plugins/UniTask/Runtime/Internal/ArrayUtil.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23146a82ec99f2542a87971c8d3d7988
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
225
Assets/Plugins/UniTask/Runtime/Internal/ContinuationQueue.cs
Normal file
225
Assets/Plugins/UniTask/Runtime/Internal/ContinuationQueue.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal sealed class ContinuationQueue
|
||||
{
|
||||
const int MaxArrayLength = 0X7FEFFFFF;
|
||||
const int InitialSize = 16;
|
||||
|
||||
readonly PlayerLoopTiming timing;
|
||||
|
||||
SpinLock gate = new SpinLock(false);
|
||||
bool dequing = false;
|
||||
|
||||
int actionListCount = 0;
|
||||
Action[] actionList = new Action[InitialSize];
|
||||
|
||||
int waitingListCount = 0;
|
||||
Action[] waitingList = new Action[InitialSize];
|
||||
|
||||
public ContinuationQueue(PlayerLoopTiming timing)
|
||||
{
|
||||
this.timing = timing;
|
||||
}
|
||||
|
||||
public void Enqueue(Action continuation)
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
|
||||
if (dequing)
|
||||
{
|
||||
// Ensure Capacity
|
||||
if (waitingList.Length == waitingListCount)
|
||||
{
|
||||
var newLength = waitingListCount * 2;
|
||||
if ((uint)newLength > MaxArrayLength) newLength = MaxArrayLength;
|
||||
|
||||
var newArray = new Action[newLength];
|
||||
Array.Copy(waitingList, newArray, waitingListCount);
|
||||
waitingList = newArray;
|
||||
}
|
||||
waitingList[waitingListCount] = continuation;
|
||||
waitingListCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Ensure Capacity
|
||||
if (actionList.Length == actionListCount)
|
||||
{
|
||||
var newLength = actionListCount * 2;
|
||||
if ((uint)newLength > MaxArrayLength) newLength = MaxArrayLength;
|
||||
|
||||
var newArray = new Action[newLength];
|
||||
Array.Copy(actionList, newArray, actionListCount);
|
||||
actionList = newArray;
|
||||
}
|
||||
actionList[actionListCount] = continuation;
|
||||
actionListCount++;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
public int Clear()
|
||||
{
|
||||
var rest = actionListCount + waitingListCount;
|
||||
|
||||
actionListCount = 0;
|
||||
actionList = new Action[InitialSize];
|
||||
|
||||
waitingListCount = 0;
|
||||
waitingList = new Action[InitialSize];
|
||||
|
||||
return rest;
|
||||
}
|
||||
|
||||
// delegate entrypoint.
|
||||
public void Run()
|
||||
{
|
||||
// for debugging, create named stacktrace.
|
||||
#if DEBUG
|
||||
switch (timing)
|
||||
{
|
||||
case PlayerLoopTiming.Initialization:
|
||||
Initialization();
|
||||
break;
|
||||
case PlayerLoopTiming.LastInitialization:
|
||||
LastInitialization();
|
||||
break;
|
||||
case PlayerLoopTiming.EarlyUpdate:
|
||||
EarlyUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastEarlyUpdate:
|
||||
LastEarlyUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.FixedUpdate:
|
||||
FixedUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastFixedUpdate:
|
||||
LastFixedUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.PreUpdate:
|
||||
PreUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastPreUpdate:
|
||||
LastPreUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.Update:
|
||||
Update();
|
||||
break;
|
||||
case PlayerLoopTiming.LastUpdate:
|
||||
LastUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.PreLateUpdate:
|
||||
PreLateUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastPreLateUpdate:
|
||||
LastPreLateUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.PostLateUpdate:
|
||||
PostLateUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastPostLateUpdate:
|
||||
LastPostLateUpdate();
|
||||
break;
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
case PlayerLoopTiming.TimeUpdate:
|
||||
TimeUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastTimeUpdate:
|
||||
LastTimeUpdate();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#else
|
||||
RunCore();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Initialization() => RunCore();
|
||||
void LastInitialization() => RunCore();
|
||||
void EarlyUpdate() => RunCore();
|
||||
void LastEarlyUpdate() => RunCore();
|
||||
void FixedUpdate() => RunCore();
|
||||
void LastFixedUpdate() => RunCore();
|
||||
void PreUpdate() => RunCore();
|
||||
void LastPreUpdate() => RunCore();
|
||||
void Update() => RunCore();
|
||||
void LastUpdate() => RunCore();
|
||||
void PreLateUpdate() => RunCore();
|
||||
void LastPreLateUpdate() => RunCore();
|
||||
void PostLateUpdate() => RunCore();
|
||||
void LastPostLateUpdate() => RunCore();
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
void TimeUpdate() => RunCore();
|
||||
void LastTimeUpdate() => RunCore();
|
||||
#endif
|
||||
|
||||
[System.Diagnostics.DebuggerHidden]
|
||||
void RunCore()
|
||||
{
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
if (actionListCount == 0) return;
|
||||
dequing = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < actionListCount; i++)
|
||||
{
|
||||
|
||||
var action = actionList[i];
|
||||
actionList[i] = null;
|
||||
try
|
||||
{
|
||||
action();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
UnityEngine.Debug.LogException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
bool lockTaken = false;
|
||||
try
|
||||
{
|
||||
gate.Enter(ref lockTaken);
|
||||
dequing = false;
|
||||
|
||||
var swapTempActionList = actionList;
|
||||
|
||||
actionListCount = waitingListCount;
|
||||
actionList = waitingList;
|
||||
|
||||
waitingListCount = 0;
|
||||
waitingList = swapTempActionList;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (lockTaken) gate.Exit(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f66c32454e50f2546b17deadc80a4c77
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
249
Assets/Plugins/UniTask/Runtime/Internal/DiagnosticsExtensions.cs
Normal file
249
Assets/Plugins/UniTask/Runtime/Internal/DiagnosticsExtensions.cs
Normal file
@@ -0,0 +1,249 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal static class DiagnosticsExtensions
|
||||
{
|
||||
static bool displayFilenames = true;
|
||||
|
||||
static readonly Regex typeBeautifyRegex = new Regex("`.+$", RegexOptions.Compiled);
|
||||
|
||||
static readonly Dictionary<Type, string> builtInTypeNames = new Dictionary<Type, string>
|
||||
{
|
||||
{ typeof(void), "void" },
|
||||
{ typeof(bool), "bool" },
|
||||
{ typeof(byte), "byte" },
|
||||
{ typeof(char), "char" },
|
||||
{ typeof(decimal), "decimal" },
|
||||
{ typeof(double), "double" },
|
||||
{ typeof(float), "float" },
|
||||
{ typeof(int), "int" },
|
||||
{ typeof(long), "long" },
|
||||
{ typeof(object), "object" },
|
||||
{ typeof(sbyte), "sbyte" },
|
||||
{ typeof(short), "short" },
|
||||
{ typeof(string), "string" },
|
||||
{ typeof(uint), "uint" },
|
||||
{ typeof(ulong), "ulong" },
|
||||
{ typeof(ushort), "ushort" },
|
||||
{ typeof(Task), "Task" },
|
||||
{ typeof(UniTask), "UniTask" },
|
||||
{ typeof(UniTaskVoid), "UniTaskVoid" }
|
||||
};
|
||||
|
||||
public static string CleanupAsyncStackTrace(this StackTrace stackTrace)
|
||||
{
|
||||
if (stackTrace == null) return "";
|
||||
|
||||
var sb = new StringBuilder();
|
||||
for (int i = 0; i < stackTrace.FrameCount; i++)
|
||||
{
|
||||
var sf = stackTrace.GetFrame(i);
|
||||
|
||||
var mb = sf.GetMethod();
|
||||
|
||||
if (IgnoreLine(mb)) continue;
|
||||
if (IsAsync(mb))
|
||||
{
|
||||
sb.Append("async ");
|
||||
TryResolveStateMachineMethod(ref mb, out var decType);
|
||||
}
|
||||
|
||||
// return type
|
||||
if (mb is MethodInfo mi)
|
||||
{
|
||||
sb.Append(BeautifyType(mi.ReturnType, false));
|
||||
sb.Append(" ");
|
||||
}
|
||||
|
||||
// method name
|
||||
sb.Append(BeautifyType(mb.DeclaringType, false));
|
||||
if (!mb.IsConstructor)
|
||||
{
|
||||
sb.Append(".");
|
||||
}
|
||||
sb.Append(mb.Name);
|
||||
if (mb.IsGenericMethod)
|
||||
{
|
||||
sb.Append("<");
|
||||
foreach (var item in mb.GetGenericArguments())
|
||||
{
|
||||
sb.Append(BeautifyType(item, true));
|
||||
}
|
||||
sb.Append(">");
|
||||
}
|
||||
|
||||
// parameter
|
||||
sb.Append("(");
|
||||
sb.Append(string.Join(", ", mb.GetParameters().Select(p => BeautifyType(p.ParameterType, true) + " " + p.Name)));
|
||||
sb.Append(")");
|
||||
|
||||
// file name
|
||||
if (displayFilenames && (sf.GetILOffset() != -1))
|
||||
{
|
||||
String fileName = null;
|
||||
|
||||
try
|
||||
{
|
||||
fileName = sf.GetFileName();
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
displayFilenames = false;
|
||||
}
|
||||
catch (SecurityException)
|
||||
{
|
||||
displayFilenames = false;
|
||||
}
|
||||
|
||||
if (fileName != null)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.AppendFormat(CultureInfo.InvariantCulture, "(at {0})", AppendHyperLink(fileName, sf.GetFileLineNumber().ToString()));
|
||||
}
|
||||
}
|
||||
|
||||
sb.AppendLine();
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
static bool IsAsync(MethodBase methodInfo)
|
||||
{
|
||||
var declareType = methodInfo.DeclaringType;
|
||||
return typeof(IAsyncStateMachine).IsAssignableFrom(declareType);
|
||||
}
|
||||
|
||||
// code from Ben.Demystifier/EnhancedStackTrace.Frame.cs
|
||||
static bool TryResolveStateMachineMethod(ref MethodBase method, out Type declaringType)
|
||||
{
|
||||
declaringType = method.DeclaringType;
|
||||
|
||||
var parentType = declaringType.DeclaringType;
|
||||
if (parentType == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var methods = parentType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance | BindingFlags.DeclaredOnly);
|
||||
if (methods == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var candidateMethod in methods)
|
||||
{
|
||||
var attributes = candidateMethod.GetCustomAttributes<StateMachineAttribute>(false);
|
||||
if (attributes == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var asma in attributes)
|
||||
{
|
||||
if (asma.StateMachineType == declaringType)
|
||||
{
|
||||
method = candidateMethod;
|
||||
declaringType = candidateMethod.DeclaringType;
|
||||
// Mark the iterator as changed; so it gets the + annotation of the original method
|
||||
// async statemachines resolve directly to their builder methods so aren't marked as changed
|
||||
return asma is IteratorStateMachineAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static string BeautifyType(Type t, bool shortName)
|
||||
{
|
||||
if (builtInTypeNames.TryGetValue(t, out var builtin))
|
||||
{
|
||||
return builtin;
|
||||
}
|
||||
if (t.IsGenericParameter) return t.Name;
|
||||
if (t.IsArray) return BeautifyType(t.GetElementType(), shortName) + "[]";
|
||||
if (t.FullName?.StartsWith("System.ValueTuple") ?? false)
|
||||
{
|
||||
return "(" + string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true))) + ")";
|
||||
}
|
||||
if (!t.IsGenericType) return shortName ? t.Name : t.FullName.Replace("Cysharp.Threading.Tasks.Triggers.", "").Replace("Cysharp.Threading.Tasks.Internal.", "").Replace("Cysharp.Threading.Tasks.", "") ?? t.Name;
|
||||
|
||||
var innerFormat = string.Join(", ", t.GetGenericArguments().Select(x => BeautifyType(x, true)));
|
||||
|
||||
var genericType = t.GetGenericTypeDefinition().FullName;
|
||||
if (genericType == "System.Threading.Tasks.Task`1")
|
||||
{
|
||||
genericType = "Task";
|
||||
}
|
||||
|
||||
return typeBeautifyRegex.Replace(genericType, "").Replace("Cysharp.Threading.Tasks.Triggers.", "").Replace("Cysharp.Threading.Tasks.Internal.", "").Replace("Cysharp.Threading.Tasks.", "") + "<" + innerFormat + ">";
|
||||
}
|
||||
|
||||
static bool IgnoreLine(MethodBase methodInfo)
|
||||
{
|
||||
var declareType = methodInfo.DeclaringType.FullName;
|
||||
if (declareType == "System.Threading.ExecutionContext")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("System.Runtime.CompilerServices"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("Cysharp.Threading.Tasks.CompilerServices"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType == "System.Threading.Tasks.AwaitTaskContinuation")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("System.Threading.Tasks.Task"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("Cysharp.Threading.Tasks.UniTaskCompletionSourceCore"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (declareType.StartsWith("Cysharp.Threading.Tasks.AwaiterActions"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static string AppendHyperLink(string path, string line)
|
||||
{
|
||||
var fi = new FileInfo(path);
|
||||
if (fi.Directory == null)
|
||||
{
|
||||
return fi.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
var fname = fi.FullName.Replace(Path.DirectorySeparatorChar, '/').Replace(PlayerLoopHelper.ApplicationDataPath, "");
|
||||
var withAssetsPath = "Assets/" + fname;
|
||||
return "<a href=\"" + withAssetsPath + "\" line=\"" + line + "\">" + withAssetsPath + ":" + line + "</a>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f80fb1c9ed4c99447be1b0a47a8d980b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
79
Assets/Plugins/UniTask/Runtime/Internal/Error.cs
Normal file
79
Assets/Plugins/UniTask/Runtime/Internal/Error.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal static class Error
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowArgumentNullException<T>(T value, string paramName)
|
||||
where T : class
|
||||
{
|
||||
if (value == null) ThrowArgumentNullExceptionCore(paramName);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void ThrowArgumentNullExceptionCore(string paramName)
|
||||
{
|
||||
throw new ArgumentNullException(paramName);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Exception ArgumentOutOfRange(string paramName)
|
||||
{
|
||||
return new ArgumentOutOfRangeException(paramName);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Exception NoElements()
|
||||
{
|
||||
return new InvalidOperationException("Source sequence doesn't contain any elements.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Exception MoreThanOneElement()
|
||||
{
|
||||
return new InvalidOperationException("Source sequence contains more than one element.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowArgumentException(string message)
|
||||
{
|
||||
throw new ArgumentException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowNotYetCompleted()
|
||||
{
|
||||
throw new InvalidOperationException("Not yet completed.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static T ThrowNotYetCompleted<T>()
|
||||
{
|
||||
throw new InvalidOperationException("Not yet completed.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void ThrowWhenContinuationIsAlreadyRegistered<T>(T continuationField)
|
||||
where T : class
|
||||
{
|
||||
if (continuationField != null) ThrowInvalidOperationExceptionCore("continuation is already registered.");
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
static void ThrowInvalidOperationExceptionCore(string message)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void ThrowOperationCanceledException()
|
||||
{
|
||||
throw new OperationCanceledException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
12
Assets/Plugins/UniTask/Runtime/Internal/Error.cs.meta
Normal file
12
Assets/Plugins/UniTask/Runtime/Internal/Error.cs.meta
Normal file
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5f39f495294d4604b8082202faf98554
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
112
Assets/Plugins/UniTask/Runtime/Internal/MinimumQueue.cs
Normal file
112
Assets/Plugins/UniTask/Runtime/Internal/MinimumQueue.cs
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
// optimized version of Standard Queue<T>.
|
||||
internal class MinimumQueue<T>
|
||||
{
|
||||
const int MinimumGrow = 4;
|
||||
const int GrowFactor = 200;
|
||||
|
||||
T[] array;
|
||||
int head;
|
||||
int tail;
|
||||
int size;
|
||||
|
||||
public MinimumQueue(int capacity)
|
||||
{
|
||||
if (capacity < 0) throw new ArgumentOutOfRangeException("capacity");
|
||||
array = new T[capacity];
|
||||
head = tail = size = 0;
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get { return size; }
|
||||
}
|
||||
|
||||
public T Peek()
|
||||
{
|
||||
if (size == 0) ThrowForEmptyQueue();
|
||||
return array[head];
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Enqueue(T item)
|
||||
{
|
||||
if (size == array.Length)
|
||||
{
|
||||
Grow();
|
||||
}
|
||||
|
||||
array[tail] = item;
|
||||
MoveNext(ref tail);
|
||||
size++;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public T Dequeue()
|
||||
{
|
||||
if (size == 0) ThrowForEmptyQueue();
|
||||
|
||||
int head = this.head;
|
||||
T[] array = this.array;
|
||||
T removed = array[head];
|
||||
array[head] = default(T);
|
||||
MoveNext(ref this.head);
|
||||
size--;
|
||||
return removed;
|
||||
}
|
||||
|
||||
void Grow()
|
||||
{
|
||||
int newcapacity = (int)((long)array.Length * (long)GrowFactor / 100);
|
||||
if (newcapacity < array.Length + MinimumGrow)
|
||||
{
|
||||
newcapacity = array.Length + MinimumGrow;
|
||||
}
|
||||
SetCapacity(newcapacity);
|
||||
}
|
||||
|
||||
void SetCapacity(int capacity)
|
||||
{
|
||||
T[] newarray = new T[capacity];
|
||||
if (size > 0)
|
||||
{
|
||||
if (head < tail)
|
||||
{
|
||||
Array.Copy(array, head, newarray, 0, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Array.Copy(array, head, newarray, 0, array.Length - head);
|
||||
Array.Copy(array, 0, newarray, array.Length - head, tail);
|
||||
}
|
||||
}
|
||||
|
||||
array = newarray;
|
||||
head = 0;
|
||||
tail = (size == capacity) ? 0 : size;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void MoveNext(ref int index)
|
||||
{
|
||||
int tmp = index + 1;
|
||||
if (tmp == array.Length)
|
||||
{
|
||||
tmp = 0;
|
||||
}
|
||||
index = tmp;
|
||||
}
|
||||
|
||||
void ThrowForEmptyQueue()
|
||||
{
|
||||
throw new InvalidOperationException("EmptyQueue");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Runtime/Internal/MinimumQueue.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/Internal/MinimumQueue.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7d63add489ccc99498114d79702b904d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
260
Assets/Plugins/UniTask/Runtime/Internal/PlayerLoopRunner.cs
Normal file
260
Assets/Plugins/UniTask/Runtime/Internal/PlayerLoopRunner.cs
Normal file
@@ -0,0 +1,260 @@
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal sealed class PlayerLoopRunner
|
||||
{
|
||||
const int InitialSize = 16;
|
||||
|
||||
readonly PlayerLoopTiming timing;
|
||||
readonly object runningAndQueueLock = new object();
|
||||
readonly object arrayLock = new object();
|
||||
readonly Action<Exception> unhandledExceptionCallback;
|
||||
|
||||
int tail = 0;
|
||||
bool running = false;
|
||||
IPlayerLoopItem[] loopItems = new IPlayerLoopItem[InitialSize];
|
||||
MinimumQueue<IPlayerLoopItem> waitQueue = new MinimumQueue<IPlayerLoopItem>(InitialSize);
|
||||
|
||||
|
||||
|
||||
public PlayerLoopRunner(PlayerLoopTiming timing)
|
||||
{
|
||||
this.unhandledExceptionCallback = ex => Debug.LogException(ex);
|
||||
this.timing = timing;
|
||||
}
|
||||
|
||||
public void AddAction(IPlayerLoopItem item)
|
||||
{
|
||||
lock (runningAndQueueLock)
|
||||
{
|
||||
if (running)
|
||||
{
|
||||
waitQueue.Enqueue(item);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
lock (arrayLock)
|
||||
{
|
||||
// Ensure Capacity
|
||||
if (loopItems.Length == tail)
|
||||
{
|
||||
Array.Resize(ref loopItems, checked(tail * 2));
|
||||
}
|
||||
loopItems[tail++] = item;
|
||||
}
|
||||
}
|
||||
|
||||
public int Clear()
|
||||
{
|
||||
lock (arrayLock)
|
||||
{
|
||||
var rest = 0;
|
||||
|
||||
for (var index = 0; index < loopItems.Length; index++)
|
||||
{
|
||||
if (loopItems[index] != null)
|
||||
{
|
||||
rest++;
|
||||
}
|
||||
|
||||
loopItems[index] = null;
|
||||
}
|
||||
|
||||
tail = 0;
|
||||
return rest;
|
||||
}
|
||||
}
|
||||
|
||||
// delegate entrypoint.
|
||||
public void Run()
|
||||
{
|
||||
// for debugging, create named stacktrace.
|
||||
#if DEBUG
|
||||
switch (timing)
|
||||
{
|
||||
case PlayerLoopTiming.Initialization:
|
||||
Initialization();
|
||||
break;
|
||||
case PlayerLoopTiming.LastInitialization:
|
||||
LastInitialization();
|
||||
break;
|
||||
case PlayerLoopTiming.EarlyUpdate:
|
||||
EarlyUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastEarlyUpdate:
|
||||
LastEarlyUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.FixedUpdate:
|
||||
FixedUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastFixedUpdate:
|
||||
LastFixedUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.PreUpdate:
|
||||
PreUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastPreUpdate:
|
||||
LastPreUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.Update:
|
||||
Update();
|
||||
break;
|
||||
case PlayerLoopTiming.LastUpdate:
|
||||
LastUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.PreLateUpdate:
|
||||
PreLateUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastPreLateUpdate:
|
||||
LastPreLateUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.PostLateUpdate:
|
||||
PostLateUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastPostLateUpdate:
|
||||
LastPostLateUpdate();
|
||||
break;
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
case PlayerLoopTiming.TimeUpdate:
|
||||
TimeUpdate();
|
||||
break;
|
||||
case PlayerLoopTiming.LastTimeUpdate:
|
||||
LastTimeUpdate();
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
break;
|
||||
}
|
||||
#else
|
||||
RunCore();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Initialization() => RunCore();
|
||||
void LastInitialization() => RunCore();
|
||||
void EarlyUpdate() => RunCore();
|
||||
void LastEarlyUpdate() => RunCore();
|
||||
void FixedUpdate() => RunCore();
|
||||
void LastFixedUpdate() => RunCore();
|
||||
void PreUpdate() => RunCore();
|
||||
void LastPreUpdate() => RunCore();
|
||||
void Update() => RunCore();
|
||||
void LastUpdate() => RunCore();
|
||||
void PreLateUpdate() => RunCore();
|
||||
void LastPreLateUpdate() => RunCore();
|
||||
void PostLateUpdate() => RunCore();
|
||||
void LastPostLateUpdate() => RunCore();
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
void TimeUpdate() => RunCore();
|
||||
void LastTimeUpdate() => RunCore();
|
||||
#endif
|
||||
|
||||
[System.Diagnostics.DebuggerHidden]
|
||||
void RunCore()
|
||||
{
|
||||
lock (runningAndQueueLock)
|
||||
{
|
||||
running = true;
|
||||
}
|
||||
|
||||
lock (arrayLock)
|
||||
{
|
||||
var j = tail - 1;
|
||||
|
||||
for (int i = 0; i < loopItems.Length; i++)
|
||||
{
|
||||
var action = loopItems[i];
|
||||
if (action != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!action.MoveNext())
|
||||
{
|
||||
loopItems[i] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
continue; // next i
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loopItems[i] = null;
|
||||
try
|
||||
{
|
||||
unhandledExceptionCallback(ex);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
|
||||
// find null, loop from tail
|
||||
while (i < j)
|
||||
{
|
||||
var fromTail = loopItems[j];
|
||||
if (fromTail != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!fromTail.MoveNext())
|
||||
{
|
||||
loopItems[j] = null;
|
||||
j--;
|
||||
continue; // next j
|
||||
}
|
||||
else
|
||||
{
|
||||
// swap
|
||||
loopItems[i] = fromTail;
|
||||
loopItems[j] = null;
|
||||
j--;
|
||||
goto NEXT_LOOP; // next i
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
loopItems[j] = null;
|
||||
j--;
|
||||
try
|
||||
{
|
||||
unhandledExceptionCallback(ex);
|
||||
}
|
||||
catch { }
|
||||
continue; // next j
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
j--;
|
||||
}
|
||||
}
|
||||
|
||||
tail = i; // loop end
|
||||
break; // LOOP END
|
||||
|
||||
NEXT_LOOP:
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
lock (runningAndQueueLock)
|
||||
{
|
||||
running = false;
|
||||
while (waitQueue.Count != 0)
|
||||
{
|
||||
if (loopItems.Length == tail)
|
||||
{
|
||||
Array.Resize(ref loopItems, checked(tail * 2));
|
||||
}
|
||||
loopItems[tail++] = waitQueue.Dequeue();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 340c6d420bb4f484aa8683415ea92571
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
50
Assets/Plugins/UniTask/Runtime/Internal/PooledDelegate.cs
Normal file
50
Assets/Plugins/UniTask/Runtime/Internal/PooledDelegate.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal sealed class PooledDelegate<T> : ITaskPoolNode<PooledDelegate<T>>
|
||||
{
|
||||
static TaskPool<PooledDelegate<T>> pool;
|
||||
|
||||
PooledDelegate<T> nextNode;
|
||||
public ref PooledDelegate<T> NextNode => ref nextNode;
|
||||
|
||||
static PooledDelegate()
|
||||
{
|
||||
TaskPool.RegisterSizeGetter(typeof(PooledDelegate<T>), () => pool.Size);
|
||||
}
|
||||
|
||||
readonly Action<T> runDelegate;
|
||||
Action continuation;
|
||||
|
||||
PooledDelegate()
|
||||
{
|
||||
runDelegate = Run;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Action<T> Create(Action continuation)
|
||||
{
|
||||
if (!pool.TryPop(out var item))
|
||||
{
|
||||
item = new PooledDelegate<T>();
|
||||
}
|
||||
|
||||
item.continuation = continuation;
|
||||
return item.runDelegate;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
void Run(T _)
|
||||
{
|
||||
var call = continuation;
|
||||
continuation = null;
|
||||
if (call != null)
|
||||
{
|
||||
pool.TryPush(this);
|
||||
call.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8932579438742fa40b010edd412dbfba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
using UnityEngine;
|
||||
#endif
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal static class RuntimeHelpersAbstraction
|
||||
{
|
||||
// If we can use RuntimeHelpers.IsReferenceOrContainsReferences(.NET Core 2.0), use it.
|
||||
public static bool IsWellKnownNoReferenceContainsType<T>()
|
||||
{
|
||||
return WellKnownNoReferenceContainsType<T>.IsWellKnownType;
|
||||
}
|
||||
|
||||
static bool WellKnownNoReferenceContainsTypeInitialize(Type t)
|
||||
{
|
||||
// The primitive types are Boolean, Byte, SByte, Int16, UInt16, Int32, UInt32, Int64, UInt64, IntPtr, UIntPtr, Char, Double, and Single.
|
||||
if (t.IsPrimitive) return true;
|
||||
|
||||
if (t.IsEnum) return true;
|
||||
if (t == typeof(DateTime)) return true;
|
||||
if (t == typeof(DateTimeOffset)) return true;
|
||||
if (t == typeof(Guid)) return true;
|
||||
if (t == typeof(decimal)) return true;
|
||||
|
||||
// unwrap nullable
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>))
|
||||
{
|
||||
return WellKnownNoReferenceContainsTypeInitialize(t.GetGenericArguments()[0]);
|
||||
}
|
||||
|
||||
#if UNITY_2018_3_OR_NEWER
|
||||
|
||||
// or add other wellknown types(Vector, etc...) here
|
||||
if (t == typeof(Vector2)) return true;
|
||||
if (t == typeof(Vector3)) return true;
|
||||
if (t == typeof(Vector4)) return true;
|
||||
if (t == typeof(Color)) return true;
|
||||
if (t == typeof(Rect)) return true;
|
||||
if (t == typeof(Bounds)) return true;
|
||||
if (t == typeof(Quaternion)) return true;
|
||||
if (t == typeof(Vector2Int)) return true;
|
||||
if (t == typeof(Vector3Int)) return true;
|
||||
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static class WellKnownNoReferenceContainsType<T>
|
||||
{
|
||||
public static readonly bool IsWellKnownType;
|
||||
|
||||
static WellKnownNoReferenceContainsType()
|
||||
{
|
||||
IsWellKnownType = WellKnownNoReferenceContainsTypeInitialize(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94975e4d4e0c0ea4ba787d3872ce9bb4
|
||||
timeCreated: 1532361007
|
||||
licenseType: Free
|
||||
MonoImporter:
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
153
Assets/Plugins/UniTask/Runtime/Internal/StatePool.cs
Normal file
153
Assets/Plugins/UniTask/Runtime/Internal/StatePool.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Cysharp.Threading.Tasks.Internal
|
||||
{
|
||||
internal static class StateTuple
|
||||
{
|
||||
public static StateTuple<T1> Create<T1>(T1 item1)
|
||||
{
|
||||
return StatePool<T1>.Create(item1);
|
||||
}
|
||||
|
||||
public static StateTuple<T1, T2> Create<T1, T2>(T1 item1, T2 item2)
|
||||
{
|
||||
return StatePool<T1, T2>.Create(item1, item2);
|
||||
}
|
||||
|
||||
public static StateTuple<T1, T2, T3> Create<T1, T2, T3>(T1 item1, T2 item2, T3 item3)
|
||||
{
|
||||
return StatePool<T1, T2, T3>.Create(item1, item2, item3);
|
||||
}
|
||||
}
|
||||
|
||||
internal class StateTuple<T1> : IDisposable
|
||||
{
|
||||
public T1 Item1;
|
||||
|
||||
public void Deconstruct(out T1 item1)
|
||||
{
|
||||
item1 = this.Item1;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StatePool<T1>.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class StatePool<T1>
|
||||
{
|
||||
static readonly ConcurrentQueue<StateTuple<T1>> queue = new ConcurrentQueue<StateTuple<T1>>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static StateTuple<T1> Create(T1 item1)
|
||||
{
|
||||
if (queue.TryDequeue(out var value))
|
||||
{
|
||||
value.Item1 = item1;
|
||||
return value;
|
||||
}
|
||||
|
||||
return new StateTuple<T1> { Item1 = item1 };
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return(StateTuple<T1> tuple)
|
||||
{
|
||||
tuple.Item1 = default;
|
||||
queue.Enqueue(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
internal class StateTuple<T1, T2> : IDisposable
|
||||
{
|
||||
public T1 Item1;
|
||||
public T2 Item2;
|
||||
|
||||
public void Deconstruct(out T1 item1, out T2 item2)
|
||||
{
|
||||
item1 = this.Item1;
|
||||
item2 = this.Item2;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StatePool<T1, T2>.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class StatePool<T1, T2>
|
||||
{
|
||||
static readonly ConcurrentQueue<StateTuple<T1, T2>> queue = new ConcurrentQueue<StateTuple<T1, T2>>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static StateTuple<T1, T2> Create(T1 item1, T2 item2)
|
||||
{
|
||||
if (queue.TryDequeue(out var value))
|
||||
{
|
||||
value.Item1 = item1;
|
||||
value.Item2 = item2;
|
||||
return value;
|
||||
}
|
||||
|
||||
return new StateTuple<T1, T2> { Item1 = item1, Item2 = item2 };
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return(StateTuple<T1, T2> tuple)
|
||||
{
|
||||
tuple.Item1 = default;
|
||||
tuple.Item2 = default;
|
||||
queue.Enqueue(tuple);
|
||||
}
|
||||
}
|
||||
|
||||
internal class StateTuple<T1, T2, T3> : IDisposable
|
||||
{
|
||||
public T1 Item1;
|
||||
public T2 Item2;
|
||||
public T3 Item3;
|
||||
|
||||
public void Deconstruct(out T1 item1, out T2 item2, out T3 item3)
|
||||
{
|
||||
item1 = this.Item1;
|
||||
item2 = this.Item2;
|
||||
item3 = this.Item3;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
StatePool<T1, T2, T3>.Return(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class StatePool<T1, T2, T3>
|
||||
{
|
||||
static readonly ConcurrentQueue<StateTuple<T1, T2, T3>> queue = new ConcurrentQueue<StateTuple<T1, T2, T3>>();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static StateTuple<T1, T2, T3> Create(T1 item1, T2 item2, T3 item3)
|
||||
{
|
||||
if (queue.TryDequeue(out var value))
|
||||
{
|
||||
value.Item1 = item1;
|
||||
value.Item2 = item2;
|
||||
value.Item3 = item3;
|
||||
return value;
|
||||
}
|
||||
|
||||
return new StateTuple<T1, T2, T3> { Item1 = item1, Item2 = item2, Item3 = item3 };
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Return(StateTuple<T1, T2, T3> tuple)
|
||||
{
|
||||
tuple.Item1 = default;
|
||||
tuple.Item2 = default;
|
||||
tuple.Item3 = default;
|
||||
queue.Enqueue(tuple);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Plugins/UniTask/Runtime/Internal/StatePool.cs.meta
Normal file
11
Assets/Plugins/UniTask/Runtime/Internal/StatePool.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 60cdf0bcaea36b444a7ae7263ae7598f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
178
Assets/Plugins/UniTask/Runtime/Internal/TaskTracker.cs
Normal file
178
Assets/Plugins/UniTask/Runtime/Internal/TaskTracker.cs
Normal file
@@ -0,0 +1,178 @@
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks.Internal;
|
||||
|
||||
namespace Cysharp.Threading.Tasks
|
||||
{
|
||||
// public for add user custom.
|
||||
|
||||
public static class TaskTracker
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
|
||||
static int trackingId = 0;
|
||||
|
||||
public const string EnableAutoReloadKey = "UniTaskTrackerWindow_EnableAutoReloadKey";
|
||||
public const string EnableTrackingKey = "UniTaskTrackerWindow_EnableTrackingKey";
|
||||
public const string EnableStackTraceKey = "UniTaskTrackerWindow_EnableStackTraceKey";
|
||||
|
||||
public static class EditorEnableState
|
||||
{
|
||||
static bool enableAutoReload;
|
||||
public static bool EnableAutoReload
|
||||
{
|
||||
get { return enableAutoReload; }
|
||||
set
|
||||
{
|
||||
enableAutoReload = value;
|
||||
UnityEditor.EditorPrefs.SetBool(EnableAutoReloadKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
static bool enableTracking;
|
||||
public static bool EnableTracking
|
||||
{
|
||||
get { return enableTracking; }
|
||||
set
|
||||
{
|
||||
enableTracking = value;
|
||||
UnityEditor.EditorPrefs.SetBool(EnableTrackingKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
static bool enableStackTrace;
|
||||
public static bool EnableStackTrace
|
||||
{
|
||||
get { return enableStackTrace; }
|
||||
set
|
||||
{
|
||||
enableStackTrace = value;
|
||||
UnityEditor.EditorPrefs.SetBool(EnableStackTraceKey, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static List<KeyValuePair<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)>> listPool = new List<KeyValuePair<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)>>();
|
||||
|
||||
static readonly WeakDictionary<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)> tracking = new WeakDictionary<IUniTaskSource, (string formattedType, int trackingId, DateTime addTime, string stackTrace)>();
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void TrackActiveTask(IUniTaskSource task, int skipFrame)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
dirty = true;
|
||||
if (!EditorEnableState.EnableTracking) return;
|
||||
var stackTrace = EditorEnableState.EnableStackTrace ? new StackTrace(skipFrame, true).CleanupAsyncStackTrace() : "";
|
||||
|
||||
string typeName;
|
||||
if (EditorEnableState.EnableStackTrace)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
TypeBeautify(task.GetType(), sb);
|
||||
typeName = sb.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
typeName = task.GetType().Name;
|
||||
}
|
||||
tracking.TryAdd(task, (typeName, Interlocked.Increment(ref trackingId), DateTime.UtcNow, stackTrace));
|
||||
#endif
|
||||
}
|
||||
|
||||
[Conditional("UNITY_EDITOR")]
|
||||
public static void RemoveTracking(IUniTaskSource task)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
dirty = true;
|
||||
if (!EditorEnableState.EnableTracking) return;
|
||||
var success = tracking.TryRemove(task);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool dirty;
|
||||
|
||||
public static bool CheckAndResetDirty()
|
||||
{
|
||||
var current = dirty;
|
||||
dirty = false;
|
||||
return current;
|
||||
}
|
||||
|
||||
/// <summary>(trackingId, awaiterType, awaiterStatus, createdTime, stackTrace)</summary>
|
||||
public static void ForEachActiveTask(Action<int, string, UniTaskStatus, DateTime, string> action)
|
||||
{
|
||||
lock (listPool)
|
||||
{
|
||||
var count = tracking.ToList(ref listPool, clear: false);
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
action(listPool[i].Value.trackingId, listPool[i].Value.formattedType, listPool[i].Key.UnsafeGetStatus(), listPool[i].Value.addTime, listPool[i].Value.stackTrace);
|
||||
listPool[i] = default;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
listPool.Clear();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void TypeBeautify(Type type, StringBuilder sb)
|
||||
{
|
||||
if (type.IsNested)
|
||||
{
|
||||
// TypeBeautify(type.DeclaringType, sb);
|
||||
sb.Append(type.DeclaringType.Name.ToString());
|
||||
sb.Append(".");
|
||||
}
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
var genericsStart = type.Name.IndexOf("`");
|
||||
if (genericsStart != -1)
|
||||
{
|
||||
sb.Append(type.Name.Substring(0, genericsStart));
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(type.Name);
|
||||
}
|
||||
sb.Append("<");
|
||||
var first = true;
|
||||
foreach (var item in type.GetGenericArguments())
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
sb.Append(", ");
|
||||
}
|
||||
first = false;
|
||||
TypeBeautify(item, sb);
|
||||
}
|
||||
sb.Append(">");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(type.Name);
|
||||
}
|
||||
}
|
||||
|
||||
//static string RemoveUniTaskNamespace(string str)
|
||||
//{
|
||||
// return str.Replace("Cysharp.Threading.Tasks.CompilerServices", "")
|
||||
// .Replace("Cysharp.Threading.Tasks.Linq", "")
|
||||
// .Replace("Cysharp.Threading.Tasks", "");
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user