Mẫu thiết kế nào phù hợp nhất với việc quản lý tay cầm cho các đối tượng, mà không vượt qua tay cầm hoặc Trình quản lý xung quanh?


8

Tôi đang viết một trò chơi bằng C ++ bằng OpenGL.

Đối với những người không biết, với API OpenGL, bạn thực hiện rất nhiều cuộc gọi đến những thứ như glGenBuffersglCreateShadervv Các loại trả về GLuintnày là các định danh duy nhất cho những gì bạn vừa tạo. Thứ được tạo ra sống trên bộ nhớ GPU.

Xem xét rằng Bộ nhớ GPU đôi khi bị hạn chế, bạn không muốn tạo hai thứ giống nhau khi chúng được sử dụng bởi nhiều đối tượng.

Ví dụ, Shader. Bạn liên kết một Chương trình Shader và sau đó bạn có một GLuint. Khi bạn hoàn thành với Shader, bạn nên gọi glDeleteShader(hoặc một cái gì đó để ảnh hưởng).

Bây giờ, giả sử tôi có một hệ thống phân cấp lớp nông như:

class WorldEntity
{
public:
    /* ... */
protected:
    ShaderProgram* shader;
    /* ... */
};

class CarEntity : public WorldEntity 
{
    /* ... */
};

class PersonEntity: public WorldEntity
{
    /* ... */
};

Bất kỳ mã nào tôi từng thấy sẽ yêu cầu tất cả các Nhà xây dựng phải ShaderProgram*truyền cho nó để được lưu trữ trong WorldEntity. ShaderProgramlà lớp của tôi gói gọn sự ràng buộc của a GLuintvới trạng thái đổ bóng hiện tại trong bối cảnh OpenGL cũng như một vài điều hữu ích khác mà bạn cần làm với Shader.

Vấn đề tôi gặp phải là:

  • Có rất nhiều tham số cần thiết để xây dựng một WorldEntity(xem xét rằng có thể có một lưới, một shader, một loạt các kết cấu, v.v., tất cả chúng có thể được chia sẻ, vì vậy chúng được chuyển qua như con trỏ)
  • Bất cứ điều gì đang tạo ra WorldEntitynhu cầu để biết những gì ShaderProgramnó cần
  • Điều này có thể yêu cầu một số loại gulp EntityManager biết được trường hợp nào ShaderProgramđể truyền cho các thực thể khác nhau.

Vì vậy, bây giờ bởi vì có Managercác lớp cần phải tự đăng ký EntityManagercùng với ShaderProgramthể hiện mà chúng cần hoặc tôi cần một switchngười quản lý lớn mà tôi cần cập nhật cho mọi WorldEntityloại dẫn xuất mới .

Suy nghĩ đầu tiên của tôi là tạo ra một ShaderManagerlớp (tôi biết, Người quản lý rất tệ) mà tôi chuyển bằng tham chiếu hoặc con trỏ đến các WorldEntitylớp để họ có thể tạo bất cứ thứ gì ShaderProgramhọ muốn, thông qua ShaderManagerShaderManagercó thể theo dõi các ShaderPrograms đã có , vì vậy nó có thể trả lại cái đã tồn tại hoặc tạo cái mới nếu cần.

(Tôi có thể lưu trữ ShaderPrograms thông qua hàm băm của tên tệp của ShaderProgrammã nguồn thực tế)

Vậy bây giờ:

  • Bây giờ tôi chuyển con trỏ đến ShaderManagerthay vì ShaderProgramvậy, vẫn còn rất nhiều tham số
  • Tôi không cần EntityManager, chính các thực thể sẽ biết thể hiện nào ShaderProgramđể tạo và ShaderManagersẽ xử lý ShaderPrograms thực tế .
  • Nhưng bây giờ tôi không biết khi nào ShaderManagercó thể xóa một cách an toàn ShaderProgram.

SO bây giờ tôi đã thêm tính tham khảo để tôi ShaderProgramlớp xóa nội bộ của mình GLuintqua glDeleteProgramvà tôi đi với ShaderManager.

Vậy bây giờ:

  • Một đối tượng có thể tạo ra bất cứ thứ gì ShaderProgramnó cần
  • Nhưng bây giờ có trùng lặp ShaderProgramvì không có Trình quản lý bên ngoài theo dõi

Cuối cùng tôi đến để đưa ra một trong hai quyết định:

1. Lớp tĩnh

A static classđược viện dẫn để tạo ShaderPrograms. Nó giữ một dấu vết nội bộ ShaderProgramdựa trên hàm băm của tên tệp - điều này có nghĩa là tôi không còn cần phải truyền con trỏ hoặc tham chiếu đến ShaderPrograms hoặc ShaderManagers xung quanh, vì vậy ít tham số hơn - Có WorldEntitiestất cả kiến ​​thức về thể hiện của ShaderProgramchúng muốn tạo

Điều này mới static ShaderManagercần:

  • giữ số lần ShaderProgramsử dụng a và tôi ShaderProgramkhông thể sao chép HOẶC
  • ShaderPrograms đếm các tham chiếu của chúng và chỉ gọi hàm glDeleteProgramhủy của chúng khi số đếm là 0AND ShaderManagerkiểm tra định kỳ ShaderProgramcác số có 1 và loại bỏ chúng.

Nhược điểm của phương pháp này tôi thấy là:

  1. Tôi có lớp tĩnh toàn cầu có thể là một vấn đề. Bối cảnh OpenGL cần được tạo trước khi gọi bất kỳ glXchức năng nào . Vì vậy, có khả năng, WorldEntitycó thể được tạo và cố gắng tạo ShaderProgramtrước khi tạo Bối cảnh OpenGL, điều này sẽ dẫn đến sự cố.

    Cách duy nhất để giải quyết vấn đề này là quay lại mọi thứ xung quanh dưới dạng con trỏ / tham chiếu hoặc có một lớp GLContext toàn cầu có thể được truy vấn hoặc giữ mọi thứ trong một lớp tạo ra Bối cảnh khi xây dựng. Hoặc có thể chỉ là một boolean toàn cầu IsContextCreatedcó thể được kiểm tra. Nhưng tôi lo lắng rằng điều này mang lại cho tôi mã xấu xí ở khắp mọi nơi.

    Những gì tôi có thể thấy sự biến thành là:

    • Lớp lớn Enginecó tất cả các lớp khác ẩn bên trong nó để nó có thể kiểm soát trật tự xây dựng / giải cấu trúc một cách thích hợp. Điều này có vẻ như là một mớ hỗn độn lớn về mã giao diện giữa người sử dụng động cơ và động cơ, giống như một trình bao bọc trên một trình bao bọc
    • Toàn bộ các lớp "Trình quản lý" theo dõi các thể hiện và xóa mọi thứ khi cần thiết. Đây có thể là một điều ác cần thiết?

  1. Khi nào thực sự xóa ShaderPrograms ra khỏi static ShaderManager? Cứ vài phút? Mỗi vòng lặp trò chơi? Tôi duyên dáng xử lý việc biên dịch lại một shader trong trường hợp khi một ShaderProgramđã bị xóa nhưng sau đó một WorldEntityyêu cầu mới ; nhưng tôi chắc chắn có một cách tốt hơn.

2. Một phương pháp tốt hơn

Đó là những gì tôi đang yêu cầu ở đây


2
Điều bạn nghĩ đến khi bạn nói "Có rất nhiều tham số cần thiết để xây dựng WorldEntity" là một mô hình nhà máy thuộc loại nào đó là những gì cần thiết để xử lý việc nối dây. Ngoài ra, tôi không nói rằng bạn nhất thiết muốn tiêm phụ thuộc ở đây, nhưng nếu bạn chưa nhìn xuống con đường đó trước khi bạn có thể thấy nó sâu sắc. Các "nhà quản lý" mà bạn đang nói ở đây nghe có vẻ giống với các trình xử lý phạm vi trọn đời.
J Trana

Vì vậy, giả sử tôi thực hiện một lớp nhà máy để xây dựng WorldEntitys; không phải là thay đổi một số vấn đề? Bởi vì bây giờ lớp WorldFactory cần truyền cho mỗi WolrdEntity chương trình ShaderPro chính xác.
NeomerArcana

Câu hỏi hay. Thông thường, không - và đây là lý do tại sao. Trong nhiều trường hợp, bạn không cần phải có Chương trình Shader cụ thể hoặc bạn có thể muốn thay đổi chương trình nào được khởi tạo hoặc có thể bạn muốn viết một bài kiểm tra đơn vị với Chương trình ShaderPro hoàn toàn mô phỏng. Một câu hỏi tôi sẽ hỏi là: nó có thực sự quan trọng đối với thực thể mà chương trình shader mà nó có không? Trong một số trường hợp có thể, nhưng vì bạn đang sử dụng con trỏ ShaderProgram chứ không phải con trỏ MySpecificShaderProgram, nên có thể không. Ngoài ra, vấn đề về phạm vi ShaderProgram bây giờ có thể chuyển sang cấp độ nhà máy, cho phép thay đổi giữa các singletons, v.v.
J Trana

Câu trả lời:


4
  1. Một phương pháp tốt hơn Đó là những gì tôi đang yêu cầu ở đây

Xin lỗi vì sự cần thiết nhưng tôi đã thấy rất nhiều vấp ngã về các vấn đề tương tự với việc quản lý tài nguyên OpenGL, bao gồm cả tôi trong quá khứ. Và rất nhiều khó khăn tôi phải vật lộn với những gì tôi nhận ra ở những người khác đến từ sự cám dỗ để bọc và đôi khi trừu tượng và thậm chí gói gọn các tài nguyên OGL cần thiết cho một số thực thể trò chơi tương tự được đưa ra.

Và "cách tốt hơn" mà tôi đã tìm thấy (ít nhất là một trong những kết thúc cuộc đấu tranh cụ thể của tôi ở đó) là làm mọi thứ theo cách khác. Điều đó có nghĩa là, đừng quan tâm đến các khía cạnh cấp thấp của OGL trong việc thiết kế các thực thể và thành phần trò chơi của bạn và tránh xa các ý tưởng như bạn Modelphải lưu trữ như một hình tam giác và đỉnh nguyên thủy dưới dạng các đối tượng bao bọc hoặc thậm chí trừu tượng VBO.

Kết xuất mối quan tâm so với mối quan tâm thiết kế trò chơi

Ví dụ, có các khái niệm cấp cao hơn một chút so với kết cấu GPU, với các yêu cầu quản lý đơn giản hơn như hình ảnh CPU (và dù sao bạn cũng cần những thứ đó, ít nhất là tạm thời, trước khi bạn thậm chí có thể tạo và liên kết kết cấu GPU). Kết xuất vắng mặt liên quan đến một mô hình có thể đủ chỉ lưu trữ một thuộc tính cho biết tên tệp sẽ sử dụng cho tệp chứa dữ liệu cho mô hình. Bạn có thể có một thành phần "vật liệu" ở mức cao hơn và trừu tượng hơn và mô tả các thuộc tính của vật liệu đó hơn là trình đổ bóng GLSL.

Và sau đó, chỉ có một nơi trong codebase liên quan đến những thứ như shader và kết cấu GPU và bối cảnh VAO / VBO và OpenGL, và đó là việc triển khai hệ thống kết xuất . Hệ thống kết xuất có thể lặp qua các thực thể trong cảnh trò chơi (trong trường hợp của tôi, nó đi qua một chỉ số không gian, nhưng bạn có thể hiểu dễ dàng hơn và bắt đầu với một vòng lặp đơn giản trước khi thực hiện tối ưu hóa như bực bội với chỉ số không gian), và nó khám phá các thành phần cấp cao của bạn như "vật liệu" và "hình ảnh" và tên tệp mô hình.

Và công việc của nó là lấy dữ liệu cấp cao hơn không liên quan trực tiếp đến GPU và tải / tạo / liên kết / liên kết / sử dụng / liên kết / hủy tài nguyên OpenGL cần thiết dựa trên những gì nó phát hiện trong cảnh và những gì xảy ra với bối cảnh. Và điều đó giúp loại bỏ sự cám dỗ sử dụng những thứ như singletons và phiên bản tĩnh của "người quản lý" và những gì không, bởi vì bây giờ tất cả quản lý tài nguyên OGL của bạn được tập trung vào một hệ thống / đối tượng trong cơ sở mã của bạn (mặc dù tất nhiên bạn có thể phân tách nó thành các đối tượng tiếp theo được gói gọn bởi trình kết xuất để làm cho mã dễ quản lý hơn). Nó cũng tự nhiên tránh được một số điểm vấp ngã với những việc như cố gắng phá hủy tài nguyên bên ngoài bối cảnh OGL hợp lệ,

Tránh thay đổi thiết kế

Hơn nữa cung cấp rất nhiều phòng thở để tránh thay đổi thiết kế trung tâm tốn kém, bởi vì nói rằng bạn phát hiện ra rằng một số vật liệu yêu cầu nhiều đường chuyền kết xuất (và nhiều shader) để kết xuất, giống như một đường chuyền tán xạ dưới bóng và đổ bóng cho vật liệu da, trong khi trước đó bạn muốn sắp xếp một vật liệu với một shader GPU duy nhất. Trong trường hợp đó, không có thay đổi thiết kế tốn kém cho các giao diện trung tâm được sử dụng bởi nhiều thứ. Tất cả những gì bạn làm là cập nhật triển khai cục bộ của hệ thống kết xuất để xử lý trường hợp trước đây không lường trước được này khi nó gặp các thuộc tính da trong thành phần vật liệu cấp cao hơn của bạn.

Chiến lược tổng thể

Và đó là chiến lược tổng thể mà tôi sử dụng bây giờ, và nó đặc biệt trở nên ngày càng hữu ích khi mối quan tâm kết xuất của bạn càng phức tạp. Như một nhược điểm, nó đòi hỏi một công việc cao hơn một chút so với việc tiêm các thực thể trò chơi của bạn bằng các shader và VBO và những thứ tương tự, và nó cũng kết hợp trình kết xuất của bạn nhiều hơn với công cụ trò chơi cụ thể của bạn (hoặc trừu tượng hóa, mặc dù đổi lại ở cấp độ cao hơn các thực thể và khái niệm trò chơi trở nên tách rời hoàn toàn khỏi các mối quan tâm kết xuất cấp thấp). Và trình kết xuất của bạn có thể cần những thứ như cuộc gọi lại để thông báo cho nó khi các thực thể bị hủy để nó có thể liên kết lại và phá hủy bất kỳ dữ liệu nào mà nó liên kết với nó (bạn có thể sử dụng tính năng giới thiệu ở đây hoặcshared_ptrcho các tài nguyên được chia sẻ, nhưng chỉ cục bộ bên trong trình kết xuất). Và bạn có thể muốn một số cách hiệu quả để liên kết và liên kết lại tất cả các loại dữ liệu kết xuất với bất kỳ thực thể nào trong thời gian không đổi (ECS có xu hướng cung cấp điều này cho mọi hệ thống với cách bạn có thể liên kết các loại thành phần mới một cách nhanh chóng nếu bạn có một ECS - nếu không, nó cũng không quá khó khăn) ... nhưng về mặt ngược lại, tất cả các loại điều này có thể sẽ hữu ích cho các hệ thống khác ngoài trình kết xuất.

Phải thừa nhận rằng việc triển khai thực tế có nhiều sắc thái hơn thế này và có thể làm mờ những thứ này nhiều hơn một chút, như động cơ của bạn có thể muốn xử lý những thứ như hình tam giác và đỉnh trong các khu vực khác ngoài kết xuất (ví dụ: vật lý có thể muốn dữ liệu đó phát hiện va chạm ). Nhưng nơi mà cuộc sống bắt đầu trở nên dễ dàng hơn (ít nhất là đối với tôi) là nắm lấy kiểu đảo ngược này trong suy nghĩ và chiến lược làm điểm khởi đầu.

Và thiết kế một trình kết xuất thời gian thực rất khó theo kinh nghiệm của tôi - điều khó nhất tôi từng thiết kế (và tiếp tục thiết kế lại) với những thay đổi nhanh chóng về phần cứng, khả năng tạo bóng, các kỹ thuật được phát hiện. Nhưng cách tiếp cận này đã loại bỏ mối quan tâm ngay lập tức khi tài nguyên GPU có thể được tạo / hủy bằng cách tập trung tất cả những gì vào việc thực hiện kết xuất, và thậm chí còn có lợi hơn cho tôi là nó đã thay đổi những gì sẽ tốn kém và thay đổi theo thiết kế (có thể tràn vào mã không liên quan ngay đến kết xuất) để chỉ thực hiện trình kết xuất. Và việc giảm chi phí thay đổi có thể tăng thêm khoản tiết kiệm rất lớn với thứ thay đổi yêu cầu hàng năm hoặc hai nhanh như kết xuất thời gian thực.

Ví dụ về bóng của bạn

Cách tôi giải quyết ví dụ về bóng mờ của bạn là tôi không quan tâm đến những thứ như bóng đổ GLSL trong những thứ như thực thể xe hơi và người. Tôi quan tâm đến "vật liệu" là các đối tượng CPU rất nhẹ chỉ chứa các thuộc tính mô tả loại vật liệu đó là gì (da, sơn xe, v.v.). Trong trường hợp thực tế của tôi, nó hơi phức tạp vì tôi có DSEL tương tự như Unep Blueprints để lập trình shader bằng cách sử dụng một loại ngôn ngữ trực quan, nhưng các tài liệu không lưu trữ tay cầm shader GLSL.

ShaderPrograms đếm số tham chiếu của chúng và chỉ gọi glDeleteProgram trong hàm hủy của chúng khi số đếm là 0 VÀ ShaderManager kiểm tra định kỳ cho ShaderProgram với số lượng 1 và loại bỏ chúng.

Tôi đã từng làm những việc tương tự khi tôi lưu trữ và quản lý các loại tài nguyên "ngoài kia trong không gian" bên ngoài trình kết xuất bởi vì những nỗ lực ngây thơ đầu tiên của tôi chỉ cố gắng phá hủy trực tiếp các tài nguyên đó trong một kẻ hủy diệt thường cố gắng phá hủy các tài nguyên đó bên ngoài Bối cảnh GL hợp lệ (và đôi khi tôi thậm chí vô tình cố gắng tạo chúng theo kịch bản hoặc thứ gì đó khi tôi không ở trong bối cảnh hợp lệ), vì vậy tôi cần trì hoãn việc tạo và hủy đối với các trường hợp tôi có thể đảm bảo tôi ở trong ngữ cảnh hợp lệ dẫn đến các thiết kế "quản lý" tương tự mà bạn mô tả.

Tất cả các vấn đề này sẽ biến mất nếu bạn lưu trữ tài nguyên CPU ở vị trí của nó và có trình kết xuất xử lý các mối quan tâm của quản lý tài nguyên GPU. Tôi không thể phá hủy trình tạo bóng OGL ở bất cứ đâu nhưng tôi có thể phá hủy vật liệu CPU ở bất cứ đâu và dễ dàng sử dụng shared_ptrmà không gặp rắc rối.

Khi nào thực sự xóa ShaderPrograms ra khỏi ShaderManager tĩnh? Cứ vài phút? Mỗi vòng lặp trò chơi? Tôi duyên dáng xử lý việc biên dịch lại một shader trong trường hợp khi ShaderProgram bị xóa nhưng sau đó một WorldEntity mới yêu cầu nó; nhưng tôi chắc chắn có một cách tốt hơn.

Bây giờ mối quan tâm đó thực sự khó khăn ngay cả trong trường hợp của tôi nếu bạn muốn quản lý hiệu quả tài nguyên GPU và giảm tải chúng khi không còn cần thiết. Trong trường hợp của tôi, tôi có thể xử lý các cảnh lớn và tôi làm việc trong VFX chứ không phải các trò chơi mà các nghệ sĩ có thể có nội dung đặc biệt dữ dội không được tối ưu hóa để hiển thị thời gian thực (kết cấu sử thi, mô hình trải dài hàng triệu đa giác, v.v.).

Nó rất hữu ích cho hiệu suất không chỉ đơn thuần là tránh hiển thị chúng khi chúng ở ngoài màn hình (ngoài tầm nhìn) mà còn giảm tài nguyên GPU khi không còn cần thiết trong một thời gian nữa (giả sử người dùng không nhìn vào thứ gì đó ở xa không gian trong một thời gian).

Vì vậy, giải pháp tôi có xu hướng sử dụng thường xuyên nhất là loại giải pháp "đánh dấu thời gian", mặc dù tôi không chắc nó có thể áp dụng như thế nào với các trò chơi. Khi tôi bắt đầu sử dụng / ràng buộc tài nguyên để kết xuất (ví dụ: chúng vượt qua bài kiểm tra loại bỏ bực bội), tôi lưu trữ thời gian hiện tại với chúng. Sau đó, định kỳ có một kiểm tra để xem liệu các tài nguyên đó đã không được sử dụng trong một thời gian và nếu vậy, chúng sẽ được tải / hủy (mặc dù dữ liệu CPU ban đầu được sử dụng để tạo tài nguyên GPU được giữ cho đến khi thực thể lưu trữ các thành phần đó bị phá hủy hoặc cho đến khi những thành phần đó được loại bỏ khỏi thực thể). Khi số lượng tài nguyên tăng lên và sử dụng nhiều bộ nhớ hơn, hệ thống sẽ trở nên tích cực hơn trong việc dỡ / hủy các tài nguyên đó (lượng thời gian nhàn rỗi cho phép cũ,

Tôi tưởng tượng nó phụ thuộc rất nhiều vào thiết kế trò chơi của bạn. Vì nếu bạn có một trò chơi với cách tiếp cận được phân đoạn nhiều hơn như các cấp / vùng nhỏ hơn, thì bạn có thể (và tìm tốc độ khung hình ổn định nhất thời gian ổn định) tải tất cả các tài nguyên cần thiết cho cấp đó trước và dỡ chúng ra khi người dùng đi đến cấp độ tiếp theo. Trong khi đó, nếu bạn có một trò chơi thế giới mở rộng lớn, liền mạch theo cách đó, bạn có thể cần một chiến lược tinh vi hơn nhiều để kiểm soát khi nào tạo và phá hủy các tài nguyên này, và có thể có một thách thức lớn hơn để làm tất cả những điều này mà không nói lắp. Trong miền VFX của tôi, một chút trục trặc về tốc độ khung hình không phải là vấn đề lớn (tôi cố gắng loại bỏ chúng trong lý do) vì người dùng sẽ không chơi game vì kết quả của nó.

Tất cả sự phức tạp này trong trường hợp của tôi vẫn bị cô lập với hệ thống kết xuất và trong khi tôi đã khái quát các lớp và mã để giúp thực hiện nó, không có lo ngại nào về bối cảnh GL và các cám dỗ hợp lệ để sử dụng toàn cầu hoặc bất cứ điều gì tương tự.


1

Thay vì thực hiện đếm tham chiếu trong ShaderProgramchính lớp, tốt hơn là ủy thác điều đó cho một lớp con trỏ thông minh, như thế std::shared_ptr<>. Bằng cách đó, bạn đảm bảo rằng mỗi lớp chỉ có một công việc duy nhất để làm.

Để tránh việc vô tình làm cạn kiệt tài nguyên OpenGL của bạn, bạn có thể tạo ShaderProgramcác bản sao không thể sao chép (riêng / xóa bản dựng và toán tử gán-sao chép).
Để giữ một kho lưu trữ trung tâm của các ShaderProgramphiên bản có thể được chia sẻ, bạn có thể sử dụng một SharedShaderProgramFactory(tương tự như trình quản lý tĩnh của bạn, nhưng với một tên tốt hơn) như thế này:

class SharedShaderProgramFactory {
private:
  std::weak_ptr<ShaderProgram> program_a;

  std::shared_ptr<ShaderProgram> get_progam_a()
  {
    shared_ptr<ShaderProgram> temp = program_a.lock();
    if (!temp)
    {
      // Requested program does not currently exist, so (re-)create it
      temp = new ShaderProgramA();
      program_a = temp; // Save for future requests
    }
    return temp;
  }
};

Lớp nhà máy có thể được triển khai như một lớp tĩnh, Singleton hoặc một phụ thuộc được thông qua khi cần thiết.


-3

Opengl đã được thiết kế như một thư viện C và nó có các đặc tính của phần mềm thủ tục. Một trong những quy tắc opengl đến từ việc trở thành một thư viện C trông như thế này:

"Khi độ phức tạp của cảnh của bạn tăng lên, bạn sẽ có nhiều tay cầm cần được truyền xung quanh mã"

Đây là tính năng của api opengl. Về cơ bản, nó giả định rằng toàn bộ mã của bạn nằm trong hàm main () và tất cả các thẻ điều khiển đó được chuyển qua các biến cục bộ của main ().

Các quy tắc của quy tắc này như sau:

  1. Bạn không nên cố gắng đặt một loại hoặc giao diện cho đường dẫn truyền dữ liệu. Lý do là loại này sẽ không ổn định, đòi hỏi phải thay đổi liên tục khi độ phức tạp của cảnh của bạn tăng lên.
  2. Đường dẫn truyền dữ liệu phải ở trong hàm main ().

Nếu được quan tâm để biết lý do tại sao điều này đang thu hút phiếu bầu. Là một người không quen thuộc với chủ đề này, sẽ rất hữu ích khi biết có gì sai với câu trả lời này.
RubberDuck

1
Tôi đã không bỏ phiếu ở đây và cũng tò mò, nhưng có lẽ ý tưởng rằng OGL được thiết kế xung quanh toàn bộ mã bên trong maincó vẻ hơi khó đối với tôi (ít nhất là theo cách diễn đạt).
Năng lượng rồng
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.