Làm thế nào người ta có thể thực hiện các mô-đun C ++ có thể hoán đổi nóng?


39

Thời gian lặp nhanh là chìa khóa để phát triển trò chơi, nhiều hơn so với đồ họa và động cơ ưa thích với tải trọng các tính năng theo ý kiến ​​của tôi. Không có gì ngạc nhiên khi nhiều nhà phát triển nhỏ chọn ngôn ngữ kịch bản.

Cách Unity 3D có thể tạm dừng trò chơi và sửa đổi tài sản VÀ mã, sau đó tiếp tục và các thay đổi có hiệu lực ngay lập tức là điều tuyệt vời cho việc này. Câu hỏi của tôi là, có ai đã triển khai một hệ thống tương tự trên các công cụ trò chơi C ++ chưa?

Tôi đã nghe nói rằng một số công cụ thực sự siêu cao cấp làm, nhưng tôi quan tâm hơn đến việc tìm hiểu xem có cách nào để làm điều đó trong một công cụ hoặc trò chơi tự trồng tại nhà.

Rõ ràng là sẽ có sự đánh đổi và tôi không thể tưởng tượng rằng bạn chỉ có thể biên dịch lại mã của mình trong khi trò chơi bị tạm dừng, để nó được tải lại và hoạt động trong mọi tình huống hoặc mọi nền tảng.

Nhưng có lẽ lập trình AI và các mô đun logic cấp đơn giản. Không phải là tôi muốn làm điều đó trong bất kỳ dự án nào trong ngắn hạn (hoặc dài hạn), nhưng tôi đã tò mò.

Câu trả lời:


26

Điều đó chắc chắn có thể làm được, mặc dù không phải với mã C ++ điển hình của bạn; bạn sẽ cần tạo một thư viện kiểu C để bạn có thể tự động liên kết và tải lại khi chạy. Để thực hiện điều này, thư viện phải chứa tất cả trạng thái trong một con trỏ mờ có thể được cung cấp cho thư viện khi tải lại.

Timothy Farrar thảo luận về cách tiếp cận của mình:

"Để phát triển mã được biên dịch thành thư viện, một chương trình nạp nhỏ tạo một bản sao của thư viện và tải bản sao thư viện để chạy chương trình. Chương trình phân bổ tất cả dữ liệu khi khởi động và sử dụng một con trỏ để tham chiếu bất kỳ dữ liệu nào. Tôi không sử dụng gì trên toàn cầu ngoại trừ dữ liệu chỉ đọc. Trong khi chương trình đang chạy thư viện gốc có thể được biên dịch lại. Sau đó, trong một lần nhấn phím, chương trình sẽ trả về trình tải và trả về con trỏ dữ liệu đó. Trình tải tạo một bản sao của thư viện mới, tải bản sao và chuyển con trỏ dữ liệu sang mã mới. Sau đó, động cơ tiếp tục nơi nó rời đi. " ( nguồn gốc , hiện có sẵn thông qua lưu trữ web )


Tôi thích câu trả lời này của Blair hơn, vì bạn thực sự trả lời câu hỏi.
Jonathan Dickinson

15

Hoán đổi nóng là một vấn đề rất, rất khó giải quyết với mã nhị phân. Ngay cả khi mã của bạn được đặt vào các thư viện liên kết động riêng biệt, mã của bạn cần đảm bảo rằng không có tham chiếu trực tiếp đến các hàm trong bộ nhớ (vì địa chỉ của các hàm có thể thay đổi khi biên dịch lại). Điều này về cơ bản có nghĩa là không sử dụng các hàm ảo và bất cứ điều gì sử dụng các con trỏ hàm đều thực hiện thông qua một số loại bảng điều phối.

Lông, công cụ ràng buộc. Tốt hơn là sử dụng ngôn ngữ kịch bản cho mã cần lặp lại nhanh.

Trong công việc, cơ sở mã hiện tại của chúng tôi là sự pha trộn của C ++ và Lua. Lua cũng không phải là một phần nhỏ trong dự án của chúng tôi - nó gần như là một sự chia tách 50/50. Chúng tôi đã triển khai tải lại Lua một cách nhanh chóng, để bạn có thể thay đổi một dòng mã, tải lại và tiếp tục. Trong thực tế, bạn có thể làm điều này để sửa lỗi sự cố xảy ra trong mã Lua, mà không cần khởi động lại trò chơi!


3
Một điểm quan trọng khác là ngay cả khi bạn không có ý định giữ một hệ thống theo kịch bản, nếu bạn muốn lặp lại sớm, thì vẫn có thể hoàn toàn hợp lý để bắt đầu ở Lua, sau đó chuyển sang C ++ khi bạn cần hiệu suất bổ sung và tốc độ lặp lại đã chậm lại. Hạn chế rõ ràng ở đây là bạn kết thúc việc chuyển mã, nhưng rất nhiều lần làm cho logic đúng là phần khó - mã gần như tự viết khi bạn tìm ra phần đó.
Logan Kincaid

15

(Bạn có thể muốn biết về thuật ngữ "khỉ vá" hoặc "đấm vịt" nếu không có gì khác ngoài hình ảnh tinh thần hài hước.)

Điều đó sang một bên: nếu mục tiêu của bạn đang giảm thời gian lặp lại cho các thay đổi "hành vi", hãy thử một số cách tiếp cận giúp bạn đạt được điều đó và kết hợp độc đáo để kích hoạt nhiều hơn điều này trong tương lai.

(Điều này sẽ xảy ra một chút tiếp tuyến, nhưng tôi hứa nó sẽ trở lại!)

  • Bắt đầu với dữ liệu và bắt đầu nhỏ: tải lại ở các ranh giới ("cấp độ" hoặc tương tự), sau đó tìm cách sử dụng chức năng của hệ điều hành để nhận thông báo thay đổi tệp hoặc chỉ cần thăm dò ý kiến thường xuyên.
  • (Đối với điểm thưởng và thời gian tải thấp hơn (một lần nữa, giảm thời gian lặp lại) hãy xem xét nướng dữ liệu .)
  • Các tập lệnh là dữ liệu và cho phép bạn lặp lại hành vi. Nếu bạn sử dụng ngôn ngữ kịch bản, giờ đây bạn có các thông báo / khả năng tải lại các tập lệnh đó, được giải thích hoặc biên dịch. Bạn cũng có thể nối trình thông dịch của mình vào bảng điều khiển trong trò chơi, ổ cắm mạng hoặc tương tự để tăng tính linh hoạt thời gian chạy.
  • Mã cũng có thể là dữ liệu : trình biên dịch của bạn có thể hỗ trợ lớp phủ , thư viện dùng chung, DLL hoặc tương tự. Vì vậy, bây giờ bạn có thể chọn thời gian "an toàn" để tải và tải lại lớp phủ hoặc DLL, cho dù là thủ công hay tự động. Các câu trả lời khác đi vào chi tiết ở đây. Xin lưu ý rằng một số biến thể của điều này có thể gây rối với mã hóa chữ ký mã hóa, bit NX (không thực thi) hoặc các cơ chế bảo mật tương tự.
  • Hãy xem xét một hệ thống lưu / tải sâu, phiên bản sâu . Nếu bạn có thể lưu và khôi phục trạng thái của mình một cách mạnh mẽ ngay cả khi đối mặt với các thay đổi mã, bạn có thể tắt trò chơi của mình và khởi động lại nó bằng logic mới tại cùng một điểm. Nói dễ hơn làm, nhưng có thể thực hiện được, và nó dễ dàng và dễ mang theo hơn là chọc bộ nhớ để thay đổi hướng dẫn.
  • Tùy thuộc vào cấu trúc và tính quyết định của trò chơi của bạn, bạn có thể thực hiện ghi và phát lại . Nếu bản ghi đó chỉ hơn "lệnh trò chơi" (ví dụ như nghĩ về một trò chơi bài), bạn có thể thay đổi tất cả mã kết xuất bạn muốn và phát lại bản ghi để xem các thay đổi của bạn. Đối với một số trò chơi, điều này "dễ dàng" như ghi lại một số tham số bắt đầu (ví dụ: một hạt giống ngẫu nhiên) và sau đó là hành động của người dùng. Đối với một số người thì phức tạp hơn nhiều.
  • Hãy nỗ lực để giảm thời gian biên dịch . Kết hợp với các hệ thống lưu / tải hoặc ghi / phát lại đã nói ở trên, hoặc thậm chí với các lớp phủ hoặc DLL, điều này có thể làm giảm vòng quay của bạn nhiều hơn bất kỳ điều gì khác.

Nhiều điểm trong số này có lợi ngay cả khi bạn không có cách nào để tải lại dữ liệu hoặc mã.

Hỗ trợ giai thoại:

Trên một PC RTS lớn (~ 120 người, chủ yếu là C ++), có một hệ thống tiết kiệm trạng thái cực kỳ sâu sắc, được sử dụng cho ít nhất ba mục đích:

  • Một bản lưu "nông" được cung cấp không phải vào đĩa mà cho động cơ CRC để đảm bảo rằng các trò chơi nhiều người chơi vẫn ở dạng mô phỏng bước khóa một CRC cứ sau 10-30 khung hình; điều này đảm bảo không ai gian lận và bắt gặp lỗi desync một vài khung hình sau đó
  • Nếu và khi xảy ra lỗi desync nhiều người chơi, một lần lưu cực sâu đã được thực hiện ở mọi khung hình và một lần nữa được cung cấp cho công cụ CRC, nhưng lần này, công cụ CRC sẽ tạo ra nhiều CRC, mỗi đợt cho các byte nhỏ hơn. Theo cách này, nó có thể cho bạn biết chính xác phần nào của trạng thái đã bắt đầu phân kỳ trong khung cuối cùng. Chúng tôi đã bắt gặp một sự khác biệt "chế độ điểm nổi mặc định" khó chịu giữa bộ xử lý AMD và Intel bằng cách sử dụng này.
  • Lưu độ sâu thông thường có thể không lưu, ví dụ: khung hoạt hình chính xác mà đơn vị bạn đang phát, nhưng nó sẽ có được vị trí, sức khỏe, v.v. của tất cả các đơn vị của bạn, cho phép bạn lưu và tiếp tục bất cứ lúc nào trong khi chơi trò chơi.

Kể từ đó, tôi đã sử dụng bản ghi / phát lại xác định trên trò chơi thẻ C ++ và Lua cho DS. Chúng tôi đã nối vào API mà chúng tôi thiết kế cho AI (về phía C ++) và ghi lại tất cả các hành động của người dùng và AI. Chúng tôi đã sử dụng chức năng này trong trò chơi (để cung cấp phát lại cho người chơi), nhưng cũng để chẩn đoán các vấn đề: khi có sự cố hoặc hành vi kỳ quặc, tất cả những gì chúng tôi phải làm là lấy tệp lưu và phát lại trong bản dựng gỡ lỗi.

Kể từ đó, tôi cũng đã sử dụng lớp phủ hơn một vài lần và chúng tôi đã kết hợp nó với hệ thống "tự động nhện thư mục này và tải nội dung mới lên hệ thống cầm tay". Tất cả những gì chúng ta phải làm là để lại cutscene / level / bất cứ thứ gì và quay trở lại, và không chỉ dữ liệu mới (sprite, layout layout, v.v.) sẽ tải mà còn bất kỳ mã mới nào trong lớp phủ. Thật không may, điều đó trở nên khó khăn hơn nhiều với các thiết bị cầm tay gần đây hơn do các cơ chế chống sao chép và chống hack đặc biệt xử lý mã. Chúng tôi vẫn làm điều đó cho các kịch bản lua mặc dù.

Cuối cùng nhưng không kém phần quan trọng: bạn có thể (và tôi có, trong những trường hợp cụ thể rất nhỏ khác nhau) thực hiện một chút cú đấm bằng cách vá trực tiếp các lệnh opcodes. Tuy nhiên, điều này hoạt động tốt nhất nếu bạn đang ở trên một nền tảng và trình biên dịch cố định, và vì nó gần như không thể nhận ra, rất dễ bị lỗi và bị hạn chế trong những gì bạn có thể thực hiện nhanh chóng, tôi chủ yếu chỉ sử dụng nó để định tuyến lại mã trong khi gỡ lỗi. Nó không dạy cho bạn một địa ngục của rất nhiều về kiến trúc tập lệnh của bạn trong một vội vàng, mặc dù.


6

Bạn có thể làm một cái gì đó như các mô-đun hoán đổi nóng trong thời gian chạy triển khai các mô đun như các thư viện liên kết động (hoặc thư viện chia sẻ trên UNIX) và sử dụng dlopen () và dlsym () để tải các hàm một cách tự nhiên từ thư viện.

Đối với Windows, tương đương là LoadL Library và GetProcAddress.

Đây là một phương thức C và có một số cạm bẫy bằng cách sử dụng nó trong C ++, bạn có thể đọc về điều đó ở đây .


hướng dẫn đó là chìa khóa để tạo ra các thư viện có thể hoán đổi nóng, cảm ơn bạn
JqueryToAddNumbers 16/07/13

4

Nếu bạn đang sử dụng Visual Studio C ++, trên thực tế bạn có thể tạm dừng và biên dịch lại mã của mình trong một số trường hợp nhất định. Visual Studio hỗ trợ Chỉnh sửa và Tiếp tục . Đính kèm vào trò chơi của bạn trong trình gỡ lỗi, làm cho nó dừng ở điểm dừng và sau đó sửa đổi mã sau điểm dừng của bạn. Nếu bạn muốn lưu và sau đó tiếp tục, Visual Studio sẽ cố gắng biên dịch lại mã, cài đặt lại nó vào tệp thực thi đang chạy và tiếp tục. Nếu mọi việc suôn sẻ, thay đổi bạn thực hiện sẽ áp dụng trực tiếp cho trò chơi đang chạy mà không phải thực hiện toàn bộ chu trình biên dịch-xây dựng-kiểm tra. Tuy nhiên, những điều sau đây sẽ ngăn điều này hoạt động:

  1. Thay đổi chức năng gọi lại sẽ không hoạt động đúng. E & C hoạt động bằng cách thêm một đoạn mã mới và thay đổi bảng cuộc gọi để trỏ đến khối mới. Đối với các cuộc gọi lại và bất cứ điều gì sử dụng con trỏ hàm, nó vẫn sẽ thực hiện các cuộc gọi cũ, chưa sửa đổi. Để khắc phục điều này, bạn có thể có chức năng gọi lại trình bao bọc gọi hàm tĩnh
  2. Sửa đổi tập tin tiêu đề sẽ gần như không bao giờ làm việc. Điều này được thiết kế để sửa đổi các cuộc gọi chức năng thực tế.
  3. Cấu trúc ngôn ngữ khác nhau sẽ khiến nó thất bại một cách bí ẩn. Từ kinh nghiệm cá nhân của tôi, việc khai báo trước những thứ như enums thường sẽ làm điều này.

Tôi đã sử dụng Chỉnh sửa và Tiếp tục để cải thiện đáng kể tốc độ của những thứ như Lặp lại UI. Giả sử bạn có một giao diện người dùng hoạt động được xây dựng hoàn toàn bằng mã ngoại trừ việc bạn vô tình hoán đổi thứ tự bốc thăm của hai hộp để bạn không thể thấy bất cứ điều gì. Bằng cách thay đổi 2 dòng mã trực tiếp, bạn có thể tiết kiệm cho mình một chu trình biên dịch / xây dựng / kiểm tra 20 phút để kiểm tra sửa lỗi UI tầm thường.

Đây KHÔNG phải là một giải pháp đầy đủ cho môi trường sản xuất và tôi đã tìm thấy giải pháp tốt nhất là chuyển càng nhiều logic của bạn càng tốt vào các tệp dữ liệu và sau đó làm cho các tệp dữ liệu đó có thể tải lại được.


chu trình biên dịch nào mất 20 phút?!? Tôi muốn tự bắn mình
JqueryToAddNumbers

3

Như những người khác đã nói, đó là một vấn đề khó khăn, liên kết động C ++. Nhưng đó là một vấn đề đã được giải quyết - bạn có thể đã nghe nói về COM hoặc một trong những tên tiếp thị đã được áp dụng cho nó trong nhiều năm qua: ActiveX.

COM có một chút tên xấu theo quan điểm của nhà phát triển bởi vì có thể rất nhiều nỗ lực để thực hiện các thành phần C ++ phơi bày chức năng của chúng bằng cách sử dụng nó (mặc dù điều này được thực hiện dễ dàng hơn với ATL - thư viện mẫu ActiveX). Từ quan điểm của người tiêu dùng, nó có một tên xấu bởi vì các ứng dụng sử dụng nó, ví dụ để nhúng bảng tính Excel vào tài liệu Word hoặc sơ đồ Visio trong bảng tính Excel, có xu hướng bị sập khá nhiều trong một ngày. Và điều đó cũng dẫn đến những vấn đề tương tự - ngay cả với tất cả các hướng dẫn mà Microsoft đưa ra, COM / ActiveX / OLE đã / rất khó để có được quyền.

Tôi sẽ nhấn mạnh rằng công nghệ của COM không phải là bản thân nó xấu. Trước hết, DirectX sử dụng giao diện COM để hiển thị chức năng của nó và hoạt động đủ tốt, cũng như vô số ứng dụng nhúng Internet Explorer bằng điều khiển ActiveX. Thứ hai, đó là một trong những cách đơn giản nhất để tự động liên kết mã C ++ - giao diện COM về cơ bản chỉ là một lớp ảo thuần túy. Mặc dù nó có IDL như CORBA, bạn không bị buộc phải sử dụng nó, đặc biệt nếu các giao diện bạn xác định chỉ được sử dụng trong dự án của bạn.

Nếu bạn không viết cho Windows, đừng nghĩ rằng COM không đáng để xem xét. Mozilla đã triển khai lại nó trong cơ sở mã của họ (được sử dụng trong trình duyệt Firefox) vì họ cần một cách để thành phần mã C ++ của họ.


2

Có một triển khai C ++ được biên dịch thời gian chạy cho mã trò chơi ở đây . Ngoài ra, tôi biết có ít nhất một công cụ trò chơi độc quyền cũng làm như vậy. Đó là khó khăn, nhưng có thể làm được.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.