Bỏ qua việc tải lại miền: lặp lại nhanh hơn bằng cách sử dụng tính năng MethodReload

Oct 22, 2026
Programming and DevOps
MethodReload

Trang web này đã được dịch bằng máy để thuận tiện cho bạn. Chúng tôi không thể đảm bảo tính chính xác hoặc độ tin cậy của nội dung được dịch. Nếu bạn có thắc mắc về tính chính xác của nội dung được dịch, vui lòng tham khảo phiên bản tiếng Anh chính thức của trang web.

Tất cả chúng ta đều đã từng trải qua: bạn chỉnh sửa một đoạn mã gameplay nào đó và sau đó chờ Editor hoàn tất việc tải lại miền. Quá trình đó có thể làm chậm thời gian lặp lại của bạn và thực sự làm cạn kiệt sự sáng tạo của bạn. Chưa kể thời gian bổ sung dành cho việc lặp lại trên thiết bị, nơi mỗi chu kỳ đòi hỏi một bản dựng đầy đủ.

Câu trả lời của chúng tôi cho điều này là MethodReload. Chúng tôi đã trình diễn nó trên sân khấu tại Unite Seoul và hiện nó có sẵn dưới dạng gói thử nghiệm. Trong bài đăng này, chúng tôi sẽ đề cập đến cách sử dụng MethodReload, những hạn chế của nó và cách nó so sánh với các công cụ hot reload mà bạn có thể đã có trong dự án của mình.

Tại sao chúng ta bị kẹt khi tải lại

Một chút lịch sử sẽ giúp ích ở đây, bởi vì lý do việc tải lại miền tồn tại cũng là lý do tại sao nó khó loại bỏ.

Chế độ Chơi được xây dựng trên AppDomains của Mono, nơi các thay đổi mã kích hoạt một quy trình quen thuộc: phá hủy domain, tải các assembly mới, tái JIT và sau đó tiếp tục trong cùng một tiến trình. Trong nhiều năm, đó là một siêu năng lực thực sự, và đó là một phần lớn lý do tại sao việc lặp lại trong Unity cảm thấy nhanh hơn so với việc xây dựng một người chơi.

Nó cũng là một công cụ thô, nghĩa là trong quá trình tải lại mọi thứ biến mất và mọi thứ trở lại, dù bạn thay đổi một phương pháp hay một trăm. Hơn nữa, IL2CPP hoàn toàn không hỗ trợ tải lại miền, vì vậy mọi thay đổi đều yêu cầu xây dựng lại toàn bộ trình chơi.

Bạn có thể đang tự hỏi, "CoreCLR sẽ sửa cái này chứ?" và câu trả lời là: Không phải bằng phép thuật. Mặc dù CoreCLR loại bỏ domain reloads, nó vẫn phải tải lại các assembly, và MethodReload cũng có thể giúp ích ở đây.

Cách sử dụng MethodReload

Như đã đề cập ở trên, MethodReload hiện đã có và được cung cấp trong com.unity.pipeline, hiện tại ở phiên bản 0.7.0-exp.1.

Quá trình sử dụng MethodReload khá đơn giản. Chỉ cần gắn thẻ một hoặc nhiều phương thức với thuộc tính [MethodReload], khởi động trình theo dõi, vào Chế độ Chơi hoặc đính kèm trình phát phát triển, và sau đó chỉnh sửa phần thân của các phương thức của bạn. Phiên bản đang chạy sẽ nhận cơ thể mới trong lần gọi tiếp theo.

Tuy nhiên, MethodReload không thay thế hoàn toàn việc tải lại miền. Những thay đổi lớn hơn vẫn phải thông qua quá trình biên dịch thông thường. Nhưng đối với những điều chỉnh nhỏ tạo nên phần lớn một vòng lặp lặp lại, nó biến một khoảng thời gian chờ đợi dài thành vấn đề vài giây, cả trong Editor và trên thiết bị.

Hãy thử xem

Cách nhanh nhất để hiểu MethodReload là chạy nó, vì vậy nếu bạn muốn tự mình thử nghiệm, đây là một số bước để bạn bắt đầu. Lưu ý: vì đây là tính năng thử nghiệm, quy trình có thể thay đổi trong tương lai và khác với những gì được mô tả ở đây

1. Cài đặt gói (yêu cầu Unity 6.0+). Bạn có thể cài đặt nó thông qua Unity CLI hoặc sử dụng Package Manager để thêm Pipeline package manually bằng cách sử dụng “Install package by technical name” và chuỗi này: com.unity.pipeline@0.7.0-exp.1.

2. Tag các phương thức bạn lặp qua. Thuộc tính đó phải có trước khi bạn vào Chế độ Chơi hoặc xây dựng người chơi, bởi vì kiểm tra phân phối được lồng vào tại thời điểm biên dịch. Các phương thức được gắn thẻ phải là public (thể hiện hoặc tĩnh) và trả về void hoặc IEnumerator; bất cứ điều gì khác sẽ bị bỏ qua ở thời điểm xây dựng với cảnh báo.

using Unity.Pipeline.MethodReload;
using UnityEngine;

public class Spinner : MonoBehaviour
{
    public float rotationSpeed = 90f;

    [MethodReload]
    public void Update()
    {
        transform.Rotate(Vector3.up, rotationSpeed * Time.deltaTime);
    }
}

3. Bắt đầu trình theo dõi. Để MethodReload của bạn thấy các thay đổi trực tiếp, bạn sẽ cần khởi động MethodReload Watcher. Làm điều này bằng cách vào Cài đặt Dự án → Pipeline → Editor, sau đó trong phần “Method Reload Interpreter Watch”, nhấp vào Start Watching.

Cài đặt dự án

Trong khi trình theo dõi bao phủ toàn bộ cây Tài sản, một tệp .cs được lưu chỉ biên dịch nếu nó chứa thuộc tính MethodReload, vì vậy các lần lưu không liên quan không tốn chi phí gì cho bạn.

4. Nếu kiểm thử trong trình chỉnh sửa: vào Chế độ Chơi. Sau khi chạy, bất kỳ thay đổi mã được gắn thẻ nào sẽ được biên dịch và tiêm tại chỗ. Khi bạn thoát khỏi chế độ chơi, các thay đổi của bạn sẽ được biên dịch và thực hiện tải lại miền thông thường.

5. Nếu kiểm tra trên thiết bị: cắm thiết bị. Xây dựng một người chơi phát triển với trình điều khiển Pipeline runtime được bật. Kết nối thiết bị với Autoconnect Profiler, hoặc đính kèm thông qua cửa sổ Profiler hoặc Console. Kể từ đó, mỗi lần lưu sẽ biên dịch trong Editor bằng các định nghĩa tiền xử lý player's và đẩy đến mọi người chơi được kết nối. Không cần khởi động lại theo bất kỳ hướng nào.

Hai chi tiết quy trình cần biết ngay từ đầu:

  • Tự động làm mới bị tắt trong khi trình theo dõi chạy, vì vậy việc lưu sẽ tải lại thay vì kích hoạt tải lại miền; các tài sản khác sẽ không được nhập cho đến khi bạn nhấn Ctrl/Cmd+R, và Dừng Theo dõi sẽ đưa cài đặt của bạn trở lại.
  • Đồng hồ sống sót qua các lần tải lại miền: nó áp dụng lại các chỉnh sửa mà việc tải lại Chế độ Chơi đã xóa, và đẩy lại cho người chơi kết nối lại, vì một người chơi khởi động lại sẽ khởi động mã đã biên dịch ban đầu.

Còn về việc sử dụng CLI thì sao?

Nếu bạn thích làm việc trong terminal, muốn làm việc thông qua một tác nhân, hoặc muốn đẩy mọi phương thức được gắn thẻ thay vì chỉ những gì đã thay đổi kể từ khi theo dõi bắt đầu, cùng chức năng đó được cung cấp dưới dạng các lệnh CLI Unity:

# Reload in the Editor through the interpreter
unity command reload_file_editor_interpreter "Assets/Scripts/Spinner.cs"

# Compile in the Editor, push to connected players (folders work too)
unity command reload_file_player_interpreter "Assets/Scripts" --player 2

Hạn chế

Không có cách nào tránh được việc tải lại miền mà không có sự thỏa hiệp, và các giới hạn của MethodReload nằm ở ba nơi: loại thay đổi nó có thể áp dụng, mã C# nó có thể chạy và tốc độ nó chạy.

Chỉ các phần thân phương thức được gắn thẻ mới thay đổi. Một lần tải lại không bao giờ chạm đến các vị trí gọi, trường hoặc bố cục kiểu. Không có trường mới, không có lớp mới. Ràng buộc đó là điều làm cho việc áp dụng nó lên một phiên bản trực tiếp trở nên an toàn, và đó là điều bạn lên kế hoạch xung quanh: quyết định những phương thức nào bạn sẽ lặp lại và gắn thẻ chúng. Một reload có thể khai báo một phương thức mới và gọi nó từ một body được tải lại, nhưng phương thức đó không thể là điểm vào reload, bởi vì không có gì được biên dịch biết phải gọi nó cho đến lần biên dịch thực tế tiếp theo của bạn.

Nó chạy một tập hợp con của C#. Trình thông dịch được chế tạo thủ công và phải đủ nhỏ để nằm trong một người chơi, vì vậy nó không triển khai tất cả các tính năng của C#. Bên trong một thân [MethodReload] bạn không thể sử dụng:

  • await và Task, try/catch/finally, lock, cú pháp truy vấn LINQ, checked
  • decimal, dynamic, các kiểu phản chiếu, các kiểu luồng
  • các tham số ref, out, và in, ref trả về và các biến cục bộ, mảng đa chiều (mảng răng cưa là được), các phương thức tổng quát

does hoạt động bao gồm hầu hết mã trò chơi: các kiểu nguyên thủy, lời gọi phương thức, vòng lặp, delegate, nội suy chuỗi, coroutine yield, Collections. Trình phân tích Roslyn đi kèm với gói và đánh dấu các vi phạm trong IDE của bạn, chỉ trên các thành phần được gắn thẻ, vì vậy bạn phát hiện ra trong khi gõ thay vì sau khi đẩy.

Trên thiết bị, mã được diễn giải chỉ có thể gọi những gì người chơi cung cấp. IL2CPP loại bỏ mã không có tham chiếu, vì vậy một cơ thể được tải lại gọi một thành viên của UnityEngine hoặc BCL mà bản dựng của bạn chưa bao giờ sử dụng sẽ không có gì để gọi. Trong Trình chỉnh sửa, các thành viên bị thiếu được liên kết theo yêu cầu. Trên thiết bị, các kiểm tra đẩy ở phía trước sẽ liệt kê các thành viên bị thiếu dưới dạng cảnh báo; nếu mã vẫn đạt đến một thành viên, nó sẽ ném ra một ngoại lệ. Quy tắc chung: thay đổi những gì một phương thức làm, chứ không phải tập hợp các API mà nó phụ thuộc vào.

Mã được thông dịch chậm hơn mã được biên dịch. Đó là bản chất của một trình thông dịch, dù là so sánh IL2CPP được biên dịch trước hay Mono và CoreCLR được biên dịch tại thời điểm chạy. Nó chỉ ảnh hưởng đến các phương thức được gắn thẻ trong các bản dựng phát triển. Trong các bản dựng phát hành không có lời nói đầu và không có trình thông dịch; một phương thức được gắn thẻ chỉ chạy phần thân đã được biên dịch của nó. Cuối cùng, việc bạn có quyết định liệu sự suy giảm hiệu suất trong quá trình lặp có đáng với tốc độ và sự tiện lợi được cải thiện trong quá trình lặp hay không.

Các coroutine nhận các thay đổi tại thời điểm tạo. Một phương thức [MethodReload] IEnumerator sử dụng phần thân mới vào lần tiếp theo StartCoroutine chạy. Các coroutine đang chạy giữ nguyên trạng thái mà chúng đã bắt đầu. Ghép nó với [OnMethodReloaded], một phương thức thể hiện không tham số kích hoạt trên mọi thể hiện đang hoạt động sau khi tải lại đến kiểu của nó, và bạn có một OnEnable không cần tải lại miền để khởi động lại các vòng lặp tồn tại lâu dài:

[OnMethodReloaded]
void OnMethodReloaded()
{
    StopAllCoroutines();
    StartCoroutine(MainLoop());
}

Nó hoạt động bên trong như thế nào

Đó là sự kết hợp của hai ý tưởng: ý đầu tiên là việc biên dịch C# tạo ra mã byte IL, và IL nằm trong một tập hợp con khiêm tốn có thể được thực thi bởi một trình thông dịch thay vì được biên dịch. Trình thông dịch đó tự nó là C# thông thường, vì vậy IL2CPP biên dịch nó cùng với phần còn lại của trò chơi của bạn, đó là điều cho phép nó chạy trên thiết bị.

Ý tưởng thứ hai là đi vào đó. Mọi phương thức [MethodReload] đều có một đường dẫn được kết nối đến trình thông dịch. Việc phân nhánh đến trình thông dịch được thực hiện bằng cách dệt một lời mở đầu vào các phương thức được gắn thẻ tại thời điểm biên dịch. Sau khi giải mã, nó trông như thế này:

[MethodReload]
public void TakeDamage(int amount)
{
    // weaved during compilation
    if (MethodReloadRegistry.HasOverride("Player.TakeDamage") &&
        MethodReloadRegistry.TryInvokeMethodReload(
            "Player.TakeDamage", this, amount))
    {
        return;
    }

    // initial code
    hp -= amount;
}

Kiểm tra dệt hỏi sổ đăng ký xem có tồn tại ghi đè nào cho phương thức này không. Người theo dõi và CLI điền vào sổ đăng ký đó bằng IL được biên dịch mới. Nếu có mục nhập, trình thông dịch sẽ chạy nó và bỏ qua phần thân gốc. Nếu không thì mã được biên dịch sẽ chạy như bình thường.

So sánh điều này

Các công cụ tốt đã tồn tại để lặp lại trên C# mà không cần tải lại miền, và chúng tôi muốn chính xác về nơi mỗi công cụ được áp dụng hơn là giả vờ như chúng không tồn tại.

Các gói từ Asset Store các phương thức vá trong thời gian chạy Mono. Điều đó mang lại cho họ trải nghiệm Editor tuyệt vời, toàn bộ C# trong bản dựng được tải lại, không cần bước gắn thẻ, và trong trường hợp Hot Reload là chế độ trên thiết bị cho các bản dựng Mono player. Tất cả những lợi thế thực sự. Điều đó cũng có nghĩa là họ không làm việc với trình chơi IL2CPP vì không có thời gian chạy để vá.

.NET Hot Reload, tính năng trong Visual Studio và Rider cho các ứng dụng .NET, áp dụng các chỉnh sửa thông qua đường dẫn chỉnh sửa và tiếp tục của runtime. Nó được xây dựng cho CoreCLR và không nhắm mục tiêu các runtime Mono hoặc IL2CPP của Unity.

MethodReload nằm ở một vị trí khác với bộ tàng hình của nó, giao diện ngôn ngữ giao dịch cho phạm vi tiếp cận: một tập hợp con của C#, nhưng chạy trên cùng một bản dựng phát triển IL2CPP mà bạn sẽ triển khai lại cho điện thoại hoặc bộ dụng cụ phát triển máy chơi game, qua cáp bạn đã cắm, mà không có máy chủ bổ sung trong người chơi và không có cổng nào để mở.

Phiên bản trung thực: nếu nỗi đau lặp lại của bạn nằm trong Trình chỉnh sửa, các công cụ hiện có đã trưởng thành và bạn nên tiếp tục sử dụng những gì phù hợp với bạn. Nếu cơn đau lặp lại của bạn tồn tại trên một thiết bị, và đặc biệt là trên IL2CPP, đây là tùy chọn đầu tiên được đưa đến đó.

Mẹo cuối cùng

Nếu bạn đang thiết lập quy trình làm việc cho một dự án ngay bây giờ:

  • Gắn thẻ các điểm nóng của bạn. [MethodReload] không tốn chi phí trong các bản dựng phát hành, vì vậy hãy gắn thẻ các phương thức Update, tinh chỉnh và cảm giác trò chơi mà bạn biết mình sẽ can thiệp trên thiết bị.
  • Hãy để bộ phân tích giữ bạn bên trong tập hợp con. Nếu một phương thức cần try/catch hoặc await, hãy di chuyển phần đó vào một hàm trợ giúp không được gắn thẻ và tải lại logic xung quanh nó. Bộ phân tích sẽ cho bạn biết nếu bạn đang làm điều gì đó sai.
  • Hãy thử nó trong Trình chỉnh sửa trước. Trình giám sát, hoặc reload_file_editor_interpreter từ CLI, chạy chỉnh sửa của bạn thông qua cùng một trình thông dịch chạy trên thiết bị. Nếu nó hoạt động ở đó, nó sẽ hoạt động trên phần cứng, vì vậy bạn bắt được các vấn đề về tập hợp con và API bị thiếu mà không cần chờ bản dựng.

Bước tiếp theo là gì

Tài liệu tham khảo lệnh, trang thiết lập thời gian chạy và danh sách ràng buộc đầy đủ nằm trong com.unity.pipeline docs.

Chúng tôi khuyến khích bạn thử nghiệm điều này trong các dự án của mình và hãy cho chúng tôi biết kết quả. Phản hồi được đánh giá cao và các gói thử nghiệm cải thiện nhanh nhất khi mọi người thúc đẩy chúng.

Tải lại miền không đi đến đâu đối với những thay đổi yêu cầu nó. Đối với những thay đổi không, bạn có thể ngừng chờ đợi.