Thực thi vi phân hoạt động như thế nào?


83

Tôi đã thấy một vài đề cập về điều này trên Stack Overflow, nhưng nhìn chằm chằm vào Wikipedia (trang có liên quan đã bị xóa) và tại bản demo hộp thoại động MFC không làm gì tôi thấy rõ. Ai đó có thể vui lòng giải thích điều này? Học một khái niệm khác về cơ bản nghe có vẻ hay.


Dựa trên các câu trả lời: Tôi nghĩ rằng tôi đang cảm thấy tốt hơn với nó. Tôi đoán là tôi đã không xem kỹ mã nguồn lần đầu tiên. Tôi có cảm giác lẫn lộn về việc thực hiện khác biệt vào thời điểm này. Một mặt, nó có thể làm cho một số nhiệm vụ dễ dàng hơn đáng kể. Mặt khác, việc thiết lập và chạy nó (nghĩa là thiết lập nó bằng ngôn ngữ bạn chọn) không phải là dễ dàng (tôi chắc rằng sẽ như vậy nếu tôi hiểu rõ hơn) ... mặc dù tôi đoán hộp công cụ dành cho nó chỉ cần được thực hiện một lần, sau đó mở rộng khi cần thiết. Tôi nghĩ để thực sự hiểu nó, có lẽ tôi sẽ cần thử triển khai nó bằng một ngôn ngữ khác.


3
Cảm ơn sự quan tâm của bạn Brian. Đối với tôi, thật thú vị khi một thứ đơn giản lại có vẻ đáng thất vọng. Với tôi, những điều đẹp đẽ nhất là đơn giản. Bảo trọng.
Mike Dunlavey

1
Tôi nghĩ rằng tôi đang thiếu một cái gì đó quan trọng. Ngay bây giờ tôi đang nghĩ, "điều này thật đơn giản." Nếu tôi thực sự hiểu nó, tôi nghĩ tôi sẽ nghĩ, "Điều này thật đơn giản. Và thực sự tuyệt vời và hữu ích."
Brian

6
... Tôi vẫn thấy mọi người giới thiệu MVC như thể đó là điều tuyệt vời nhất, và tôi nghĩ tôi thà nghỉ hưu còn hơn phải làm lại điều đó.
Mike Dunlavey

1
... để "hoàn tác", bạn tuần tự hóa / giải mã hóa dữ liệu và tách ra một tệp là XOR của cả hai, phần lớn là 0 nên dễ dàng nén. Sử dụng nó để khôi phục dữ liệu trước đó. Bây giờ tổng quát hóa thành cấu trúc dữ liệu tùy ý.
Mike Dunlavey

1
Không muốn thêm vào khối lượng công việc của bạn, @MikeDunlavey, nhưng nếu bạn bỏ lỡ nó, Source Forge đã không còn duyên với các hoạt động biz đáng ngờ. Github.com là nơi những đứa trẻ thú vị thường lui tới ngày nay. Họ có một khách hàng Windows thực sự tốt đẹp cho W7 tại desktop.github.com
GS Falken

Câu trả lời:


95

Gee, Brian, tôi ước tôi thấy câu hỏi của bạn sớm hơn. Vì nó khá giống "phát minh" của tôi (tốt hơn hoặc tệ hơn), tôi có thể giúp.

Đã chèn: Lời giải thích ngắn gọn nhất mà tôi có thể đưa ra là nếu thực hiện thông thường giống như ném một quả bóng lên không trung và bắt lấy nó, thì thực hiện vi sai giống như tung hứng.

Lời giải thích của @ windfinder khác với của tôi, và điều đó không sao cả. Kỹ thuật này không dễ để hiểu hết mọi người và tôi đã mất khoảng 20 năm (lặp đi lặp lại) để tìm ra lời giải thích hiệu quả. Hãy để tôi cung cấp cho nó một shot khác ở đây:

  • Nó là gì?

Tất cả chúng ta đều hiểu ý tưởng đơn giản về một chiếc máy tính chạy qua một chương trình, lấy các nhánh có điều kiện dựa trên dữ liệu đầu vào và thực hiện mọi việc. (Giả sử chúng ta chỉ xử lý mã goto-less, return-less có cấu trúc đơn giản.) Đoạn mã đó chứa các chuỗi câu lệnh, điều kiện có cấu trúc cơ bản, vòng lặp đơn giản và lệnh gọi chương trình con. (Hãy quên các hàm trả về giá trị ngay bây giờ.)

Bây giờ, hãy tưởng tượng hai máy tính thực thi cùng một mã đó trong bước khóa với nhau và có thể so sánh các ghi chú. Máy tính 1 chạy với dữ liệu đầu vào A và Máy tính 2 chạy với dữ liệu đầu vào B. Chúng chạy từng bước một. Nếu họ đến với một câu lệnh điều kiện như IF (test) .... ENDIF và nếu họ có ý kiến ​​khác nhau về việc kiểm tra có đúng hay không, thì người nói kiểm tra nếu sai sẽ bỏ qua ENDIF và đợi xung quanh em gái của nó để bắt kịp. (Đây là lý do tại sao mã được cấu trúc, vì vậy chúng tôi biết chị em cuối cùng sẽ nhận được ENDIF.)

Vì hai máy tính có thể nói chuyện với nhau nên chúng có thể so sánh các ghi chú và đưa ra lời giải thích chi tiết về việc hai bộ dữ liệu đầu vào và lịch sử thực thi khác nhau như thế nào.

Tất nhiên, trong thực thi vi phân (DE), nó được thực hiện với một máy tính, mô phỏng hai.

NGAY BÂY GIỜ, giả sử bạn chỉ có một bộ dữ liệu đầu vào, nhưng bạn muốn xem nó đã thay đổi như thế nào từ lần 1 sang lần 2. Giả sử chương trình bạn đang thực hiện là bộ nối tiếp / bộ khử không khí. Khi bạn thực thi, bạn vừa tuần tự hóa (ghi ra) dữ liệu hiện tại vừa giải mã hóa (đọc trong) dữ liệu quá khứ (được ghi vào lần cuối cùng bạn làm điều này). Bây giờ bạn có thể dễ dàng thấy sự khác biệt giữa dữ liệu lần trước và dữ liệu lần này là gì.

Tệp bạn đang ghi và tệp cũ bạn đang đọc từ đó, được ghép lại với nhau tạo thành một hàng đợi hoặc FIFO (nhập trước xuất trước), nhưng đó không phải là một khái niệm sâu sắc.

  • Nó tốt cho cái gì?

Điều đó xảy ra với tôi khi tôi đang làm việc trong một dự án đồ họa, nơi người dùng có thể xây dựng các quy trình nhỏ của bộ xử lý hiển thị được gọi là "ký hiệu" có thể được lắp ráp thành các quy trình lớn hơn để vẽ những thứ như sơ đồ đường ống, bể chứa, van, những thứ tương tự. Chúng tôi muốn các sơ đồ "năng động" theo nghĩa là chúng có thể tự cập nhật dần dần mà không cần phải vẽ lại toàn bộ sơ đồ. (Phần cứng chậm theo tiêu chuẩn ngày nay.) Tôi nhận ra rằng (ví dụ) một thói quen vẽ một thanh của biểu đồ thanh có thể nhớ chiều cao cũ của nó và chỉ cần cập nhật dần dần.

Điều này nghe giống như OOP, phải không? Tuy nhiên, thay vì "tạo" một "đối tượng", tôi có thể tận dụng khả năng dự đoán của trình tự thực thi của thủ tục sơ đồ. Tôi có thể viết chiều cao của thanh trong một luồng byte tuần tự. Sau đó, để cập nhật hình ảnh, tôi chỉ có thể chạy thủ tục ở chế độ mà nó tuần tự đọc các thông số cũ trong khi nó ghi các thông số mới để sẵn sàng cho lần cập nhật tiếp theo.

Điều này có vẻ rõ ràng một cách ngu ngốc và dường như sẽ bị phá vỡ ngay khi thủ tục chứa một điều kiện, vì khi đó luồng mới và luồng cũ sẽ không đồng bộ. Nhưng sau đó tôi nhận ra rằng nếu họ cũng tuần tự hóa giá trị boolean của bài kiểm tra điều kiện, họ có thể đồng bộ trở lại . Phải mất một lúc để thuyết phục bản thân, và sau đó chứng minh rằng điều này sẽ luôn hoạt động, với điều kiện tuân theo một quy tắc đơn giản ("quy tắc chế độ xóa").

Kết quả thực tế là người dùng có thể thiết kế các "ký hiệu động" này và lắp ráp chúng thành các biểu đồ lớn hơn mà không cần phải lo lắng về cách chúng sẽ cập nhật động, bất kể màn hình có biến đổi cấu trúc hoặc phức tạp đến mức nào.

Vào những ngày đó, tôi đã phải lo lắng về sự giao thoa giữa các vật thể trực quan, để việc xóa một vật thể sẽ không làm hỏng những vật thể khác. Tuy nhiên, bây giờ tôi sử dụng kỹ thuật này với các điều khiển Windows và tôi để Windows xử lý các vấn đề kết xuất.

Vậy nó đạt được những gì? Điều đó có nghĩa là tôi có thể xây dựng một hộp thoại bằng cách viết một thủ tục để vẽ các điều khiển và tôi không phải lo lắng về việc thực sự ghi nhớ các đối tượng điều khiển hoặc xử lý việc cập nhật từng bước chúng, hoặc làm cho chúng xuất hiện / biến mất / di chuyển khi có điều kiện. Kết quả là mã nguồn hộp thoại nhỏ hơn và đơn giản hơn nhiều, theo thứ tự độ lớn, và những thứ như bố cục động hoặc thay đổi số lượng điều khiển hoặc có mảng hoặc lưới điều khiển là không đáng kể. Ngoài ra, một điều khiển chẳng hạn như trường Chỉnh sửa có thể bị ràng buộc nhỏ với dữ liệu ứng dụng mà nó đang chỉnh sửa và nó sẽ luôn đúng một cách có thể chứng minh được và tôi không bao giờ phải xử lý các sự kiện của nó. Đưa vào trường chỉnh sửa cho biến chuỗi ứng dụng là chỉnh sửa một dòng.

  • Tại sao nó khó hiểu?

Điều tôi thấy khó giải thích nhất là nó đòi hỏi tư duy khác về phần mềm. Các lập trình viên gắn bó chặt chẽ với chế độ xem hành động đối tượng của phần mềm đến mức họ muốn biết đâu là đối tượng, đâu là lớp, cách họ "xây dựng" màn hình và cách họ xử lý các sự kiện, điều đó phải mất một thời gian. bom để nổ chúng ra khỏi nó. Điều tôi cố gắng truyền đạt là điều thực sự quan trọng là bạn cần nói gì?Hãy tưởng tượng bạn đang xây dựng một ngôn ngữ dành riêng cho miền (DSL) mà tất cả những gì bạn cần làm là nói với nó "Tôi muốn sửa biến A ở đây, biến B ở đó và biến C ở đó" và nó sẽ xử lý nó một cách kỳ diệu cho bạn . Ví dụ, trong Win32 có "ngôn ngữ tài nguyên" này để xác định hộp thoại. Nó là một DSL hoàn toàn tốt, ngoại trừ nó không đi đủ xa. Nó không "sống trong" ngôn ngữ thủ tục chính hoặc xử lý các sự kiện cho bạn hoặc chứa các vòng lặp / điều kiện / chương trình con. Nhưng nó có nghĩa là tốt và Hộp thoại động sẽ cố gắng hoàn thành công việc.

Vì vậy, cách suy nghĩ khác là: để viết một chương trình, trước tiên bạn phải tìm (hoặc phát minh ra) một DSL thích hợp và viết mã chương trình của bạn vào đó càng nhiều càng tốt. Để xử lý tất cả các đối tượng và hành động chỉ tồn tại cho mục đích triển khai.

Nếu bạn muốn thực sự hiểu về thực thi vi phân và sử dụng nó, có một số vấn đề phức tạp có thể khiến bạn khó chịu. Tôi đã từng mã hóa nó trong các macro Lisp , nơi những bit phức tạp này có thể được xử lý cho bạn, nhưng trong các ngôn ngữ "bình thường", nó đòi hỏi một số kỷ luật của lập trình viên để tránh những cạm bẫy.

Xin lỗi vì đã dài dòng như vậy. Nếu tôi không hiểu, tôi sẽ đánh giá cao nếu bạn chỉ ra và tôi có thể thử và sửa nó.

Thêm:

Trong Java Swing , có một chương trình ví dụ được gọi là TextInputDemo. Nó là một hộp thoại tĩnh, có 270 dòng (không tính danh sách 50 trạng thái). Trong Hộp thoại động (trong MFC), khoảng 60 dòng:

#define NSTATE (sizeof(states)/sizeof(states[0]))
CString sStreet;
CString sCity;
int iState;
CString sZip;
CString sWholeAddress;

void SetAddress(){
    CString sTemp = states[iState];
    int len = sTemp.GetLength();
    sWholeAddress.Format("%s\r\n%s %s %s", sStreet, sCity, sTemp.Mid(len-3, 2), sZip);
}

void ClearAddress(){
    sWholeAddress = sStreet = sCity = sZip = "";
}

void CDDDemoDlg::deContentsTextInputDemo(){
    int gy0 = P(gy);
    P(www = Width()*2/3);
    deStartHorizontal();
    deStatic(100, 20, "Street Address:");
    deEdit(www - 100, 20, &sStreet);
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "City:");
    deEdit(www - 100, 20, &sCity);
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "State:");
    deStatic(www - 100 - 20 - 20, 20, states[iState]);
    if (deButton(20, 20, "<")){
        iState = (iState+NSTATE - 1) % NSTATE;
        DD_THROW;
    }
    if (deButton(20, 20, ">")){
        iState = (iState+NSTATE + 1) % NSTATE;
        DD_THROW;
    }
    deEndHorizontal(20);
    deStartHorizontal();
    deStatic(100, 20, "Zip:");
    deEdit(www - 100, 20, &sZip);
    deEndHorizontal(20);
    deStartHorizontal();
    P(gx += 100);
    if (deButton((www-100)/2, 20, "Set Address")){
        SetAddress();
        DD_THROW;
    }
    if (deButton((www-100)/2, 20, "Clear Address")){
        ClearAddress();
        DD_THROW;
    }
    deEndHorizontal(20);
    P((gx = www, gy = gy0));
    deStatic(P(Width() - gx), 20*5, (sWholeAddress != "" ? sWholeAddress : "No address set."));
}

Thêm:

Đây là mã ví dụ để chỉnh sửa một mảng bệnh nhân trong bệnh viện trong khoảng 40 dòng mã. Các dòng 1-6 xác định "cơ sở dữ liệu". Các dòng 10-23 xác định nội dung tổng thể của giao diện người dùng. Các dòng 30-48 xác định các điều khiển để chỉnh sửa hồ sơ của một bệnh nhân. Lưu ý rằng hình thức của chương trình hầu như không có thông báo về các sự kiện trong thời gian, như thể tất cả những gì nó phải làm là tạo màn hình một lần. Sau đó, nếu các đối tượng được thêm vào hoặc bị xóa hoặc các thay đổi cấu trúc khác diễn ra, nó sẽ đơn giản được thực thi lại, như thể nó đang được tạo lại từ đầu, ngoại trừ việc DE khiến quá trình cập nhật gia tăng diễn ra thay thế. Ưu điểm của bạn là lập trình viên không phải chú ý hoặc viết bất kỳ mã nào để thực hiện các cập nhật gia tăng của giao diện người dùng và chúng được đảm bảo chính xác. Có vẻ như việc thực thi lại này sẽ là một vấn đề về hiệu suất, nhưng không phải vậy,

1  class Patient {public:
2    String name;
3    double age;
4    bool smoker; // smoker only relevant if age >= 50
5  };
6  vector< Patient* > patients;

10 void deContents(){ int i;
11   // First, have a label
12   deLabel(200, 20, “Patient name, age, smoker:”);
13   // For each patient, have a row of controls
14   FOR(i=0, i<patients.Count(), i++)
15     deEditOnePatient( P( patients[i] ) );
16   END
17   // Have a button to add a patient
18   if (deButton(50, 20, “Add”)){
19     // When the button is clicked add the patient
20     patients.Add(new Patient);
21     DD_THROW;
22   }
23 }

30 void deEditOnePatient(Patient* p){
31   // Determine field widths
32   int w = (Width()-50)/3;
33   // Controls are laid out horizontally
34   deStartHorizontal();
35     // Have a button to remove this patient
36     if (deButton(50, 20, “Remove”)){
37       patients.Remove(p);
37       DD_THROW;
39     }
40     // Edit fields for name and age
41     deEdit(w, 20, P(&p->name));
42     deEdit(w, 20, P(&p->age));
43     // If age >= 50 have a checkbox for smoker boolean
44     IF(p->age >= 50)
45       deCheckBox(w, 20, “Smoker?”, P(&p->smoker));
46     END
47   deEndHorizontal(20);
48 }

Đã thêm: Brian đã hỏi một câu hỏi hay và tôi nghĩ câu trả lời thuộc về văn bản chính ở đây:

@Mike: Tôi không rõ câu lệnh "if (deButton (50, 20," Add ")) {" thực sự đang làm gì. Hàm deButton làm gì? Ngoài ra, các vòng lặp FOR / END của bạn có sử dụng một số loại macro hay gì đó không? - Brian.

@Brian: Có, câu lệnh FOR / END và IF là macro. Dự án SourceForge đã được triển khai hoàn chỉnh. deButton duy trì một nút điều khiển. Khi bất kỳ hành động nhập của người dùng nào diễn ra, mã sẽ được chạy ở chế độ "sự kiện điều khiển", trong đó deButton phát hiện rằng nó đã được nhấn và cho biết rằng nó đã được nhấn bằng cách trả về TRUE. Do đó, "if (deButton (...)) {... mã hành động ...} là một cách để gắn mã hành động vào nút mà không cần phải tạo một bao đóng hoặc viết một trình xử lý sự kiện. DD_THROW là một cách kết thúc quá trình chuyển khi hành động được thực hiện vì hành động có thể đã sửa đổi dữ liệu ứng dụng, do đó, việc tiếp tục chuyển "sự kiện điều khiển" qua quy trình là không hợp lệ. Nếu bạn so sánh điều này với việc viết các trình xử lý sự kiện, nó sẽ giúp bạn viết chúng, và nó cho phép bạn có bất kỳ số lượng điều khiển nào.

Đã thêm: Xin lỗi, tôi nên giải thích ý của tôi bằng từ "duy trì". Khi thủ tục được thực thi lần đầu tiên (ở chế độ HIỂN THỊ), deButton tạo điều khiển nút và ghi nhớ id của nó trong FIFO. Trong các lần chuyển tiếp theo (ở chế độ CẬP NHẬT), deButton lấy id từ FIFO, sửa đổi nó nếu cần và đưa nó trở lại FIFO. Trong chế độ ERASE, nó đọc nó từ FIFO, phá hủy nó và không đặt nó trở lại, do đó "thu thập rác" nó. Vì vậy, lệnh gọi deButton quản lý toàn bộ thời gian tồn tại của điều khiển, giữ nó phù hợp với dữ liệu ứng dụng, đó là lý do tại sao tôi nói nó "duy trì" nó.

Chế độ thứ tư là EVENT (hoặc CONTROL). Khi người dùng nhập một ký tự hoặc nhấp vào một nút, sự kiện đó sẽ được bắt và ghi lại, sau đó thủ tục deContents được thực thi ở chế độ EVENT. deButton lấy id của điều khiển nút của nó từ FIFO và hỏi nếu đây là điều khiển đã được nhấp. Nếu đúng, nó trả về TRUE để mã hành động có thể được thực thi. Nếu không, nó chỉ trả về FALSE. Mặt khác, deEdit(..., &myStringVar)phát hiện xem sự kiện có dành cho nó hay không và nếu có thì chuyển nó đến điều khiển chỉnh sửa, rồi sao chép nội dung của điều khiển chỉnh sửa vào myStringVar. Giữa quá trình này và quá trình UPDATE thông thường, myStringVar luôn cân bằng nội dung của điều khiển chỉnh sửa. Đó là cách "ràng buộc" được thực hiện. Ý tưởng tương tự cũng áp dụng cho thanh cuộn, hộp danh sách, hộp tổ hợp, bất kỳ loại điều khiển nào cho phép bạn chỉnh sửa dữ liệu ứng dụng.

Đây là liên kết đến bản chỉnh sửa Wikipedia của tôi: http://en.wikipedia.org/wiki/User:MikeDunlavey/Difex_Article


4
Và xin lỗi khi làm bạn mắc phải câu trả lời nhưng nếu tôi hiểu điều này một cách chính xác, về cơ bản bạn đang di chuyển các tính toán của mình ngày càng gần bộ xử lý và ra khỏi phần cứng đầu ra. Đây là một cái nhìn sâu sắc đáng kinh ngạc vì chúng tôi đầu tư rất nhiều vào ý tưởng rằng bằng cách lập trình trong các đối tượng và biến, chúng khá dễ dàng được dịch sang mã máy tốt nhất để đạt được cùng một đầu ra, điều này chắc chắn không phải vậy! Mặc dù chúng tôi có thể tối ưu hóa mã khi biên dịch, nhưng không thể tối ưu hóa các hành động phụ thuộc vào thời gian. Từ chối sự phụ thuộc vào thời gian và biến những người nguyên thủy thực hiện công việc !
sova

2
@Joey: Bây giờ bạn đề cập đến nó, ý tưởng về một cấu trúc điều khiển chạy khỏi FIFO và các đồng quy trình song song chạy ra khỏi hàng đợi công việc, có rất nhiều điểm chung ở đó.
Mike Dunlavey

2
Tôi tự hỏi thực thi Khác biệt gần như thế nào với cách tiếp cận được sử dụng bởi thư viện react.js.
Brian

2
@Brian: Từ việc đọc lướt thông tin, react.js sử dụng một chức năng khác để gửi các bản cập nhật gia tăng cho trình duyệt. Tôi không thể biết liệu hàm khác có thực sự có khả năng như thực thi vi phân hay không. Giống như nó có thể xử lý các thay đổi tùy ý, và nó tuyên bố đơn giản hóa ràng buộc. Cho dù nó được thực hiện ở mức độ tương tự, tôi không biết. Dù sao, tôi nghĩ nó đang đi đúng hướng. Video cặp đôi tại đây.
Mike Dunlavey

2
@MikeDunlavey, tôi viết các công cụ của mình với sự kết hợp của OpenGL / IMGUI và lập trình phản ứng trên các lớp Model, Model-View và View. Tôi sẽ không bao giờ quay lại phong cách cũ như bây giờ. Cảm ơn các liên kết video của bạn.
Cthutu

13

Thực thi khác biệt là một chiến lược để thay đổi luồng mã của bạn dựa trên các sự kiện bên ngoài. Điều này thường được thực hiện bằng cách thao tác với một cấu trúc dữ liệu nào đó để ghi lại các thay đổi. Điều này chủ yếu được sử dụng trong giao diện người dùng đồ họa, nhưng cũng được sử dụng cho những thứ như tuần tự hóa, nơi bạn đang hợp nhất các thay đổi thành một "trạng thái" hiện có.

Quy trình cơ bản như sau:

Start loop:
for each element in the datastructure: 
    if element has changed from oldDatastructure:
        copy element from datastructure to oldDatastructure
        execute corresponding subroutine (display the new button in your GUI, for example)
End loop:
Allow the states of the datastructure to change (such as having the user do some input in the GUI)

Những lợi thế của điều này là một vài. Một, đó là sự tách biệt giữa việc thực hiện các thay đổi của bạn và thao tác thực tế đối với dữ liệu hỗ trợ. Điều này tốt cho nhiều bộ xử lý. Hai, nó cung cấp một phương pháp băng thông thấp để truyền đạt những thay đổi trong chương trình của bạn.


12

Hãy nghĩ về cách màn hình hoạt động:

Nó được cập nhật ở 60 Hz - 60 lần một giây. Nhấp nháy nhấp nháy 60 lần, nhưng mắt của bạn chậm và không thể thực sự nhận biết được. Màn hình hiển thị bất cứ thứ gì có trong bộ đệm đầu ra; nó chỉ kéo dữ liệu này ra mỗi phần 60 giây bất kể bạn làm gì.

Bây giờ tại sao bạn muốn chương trình của mình cập nhật toàn bộ bộ đệm 60 lần một giây nếu hình ảnh không nên thay đổi thường xuyên? Điều gì sẽ xảy ra nếu bạn chỉ thay đổi một pixel của hình ảnh, bạn có nên viết lại toàn bộ bộ đệm?


Đây là phần trừu tượng của ý tưởng cơ bản: bạn muốn thay đổi bộ đệm đầu ra dựa trên thông tin bạn muốn hiển thị trên màn hình. Bạn muốn tiết kiệm càng nhiều thời gian CPU và thời gian ghi bộ đệm càng tốt, vì vậy bạn không chỉnh sửa các phần của bộ đệm không cần thay đổi cho lần kéo màn hình tiếp theo.

Màn hình tách biệt với máy tính và logic (chương trình) của bạn. Nó đọc từ bộ đệm đầu ra ở bất kỳ tốc độ nào mà nó cập nhật màn hình. Chúng tôi muốn máy tính của mình ngừng đồng bộ hóa và vẽ lại một cách không cần thiết. Chúng ta có thể giải quyết vấn đề này bằng cách thay đổi cách chúng ta làm việc với bộ đệm, có thể được thực hiện theo nhiều cách khác nhau. Kỹ thuật của anh ấy thực hiện một hàng đợi FIFO bị trễ - nó chứa những gì chúng ta vừa gửi đến bộ đệm. Hàng đợi FIFO bị trì hoãn không giữ dữ liệu pixel, nó giữ "hình dạng nguyên thủy" (có thể là pixel trong ứng dụng của bạn, nhưng cũng có thể là đường thẳng, hình chữ nhật, những thứ dễ vẽ vì chúng chỉ là hình dạng, không có dữ liệu không cần thiết cho phép).

Vì vậy, bạn muốn vẽ / xóa mọi thứ khỏi màn hình? Không vấn đề gì. Dựa trên nội dung của hàng đợi FIFO, tôi biết màn hình trông như thế nào vào lúc này. Tôi so sánh đầu ra mong muốn của mình (để xóa hoặc vẽ các nguyên thủy mới) với hàng đợi FIFO và chỉ thay đổi các giá trị cần được thay đổi / cập nhật. Đây là bước mà nó có tên là Đánh giá sự khác biệt.

Hai cách khác biệt mà tôi đánh giá cao điều này:

Đầu tiên: Mike Dunlavey sử dụng phần mở rộng câu lệnh có điều kiện. Hàng đợi FIFO chứa nhiều thông tin ("trạng thái trước đó" hoặc nội dung hiện tại trên màn hình hoặc thiết bị bỏ phiếu dựa trên thời gian). Tất cả những gì bạn phải thêm vào đây là trạng thái bạn muốn xuất hiện trên màn hình tiếp theo.

Một bit có điều kiện được thêm vào mọi vị trí có thể chứa một bit nguyên thủy trong hàng đợi FIFO.

0 means erase
1 means draw

Tuy nhiên, chúng tôi có trạng thái trước đó:

Was 0, now 0: don't do anything;
Was 0, now 1: add it to the buffer (draw it);
Was 1, now 1: don't do anything;
Was 1, now 0: erase it from the buffer (erase it from the screen);

Điều này rất thanh lịch, bởi vì khi bạn cập nhật một thứ gì đó, bạn thực sự chỉ cần biết những gì nguyên thủy bạn muốn vẽ lên màn hình - so sánh này sẽ tìm ra liệu nó có nên xóa một nguyên thủy hay thêm / giữ nó vào / trong bộ đệm hay không.

Điều thứ hai: Đây chỉ là một ví dụ, và tôi nghĩ rằng những gì Mike thực sự nhận được là thứ cần thiết cơ bản trong thiết kế cho tất cả các dự án: Giảm độ phức tạp (tính toán) của thiết kế bằng cách viết các hoạt động tính toán cao nhất của bạn dưới dạng máy tính-food hoặc càng gần càng tốt. Tôn trọng thời gian tự nhiên của thiết bị.

Phương pháp vẽ lại để vẽ toàn bộ màn hình vô cùng tốn kém và có những ứng dụng khác mà thông tin chi tiết này vô cùng có giá trị.

Chúng ta không bao giờ "di chuyển" các đối tượng xung quanh màn hình. "Di chuyển" là một hoạt động tốn kém nếu chúng ta định bắt chước hành động vật lý của việc "di chuyển" khi chúng ta thiết kế mã cho một thứ như màn hình máy tính. Thay vào đó, các đối tượng về cơ bản chỉ nhấp nháy và tắt với màn hình. Mỗi khi một đối tượng di chuyển, bây giờ nó là một tập hợp nguyên thủy mới và tập hợp nguyên thủy cũ sẽ tắt.

Mỗi khi màn hình kéo từ bộ đệm, chúng tôi có các mục nhập giống như

Draw bit    primitive_description
0           Rect(0,0,5,5);
1           Circ(0,0,2);
1           Line(0,1,2,5);

Không bao giờ một đối tượng tương tác với màn hình (hoặc thiết bị bỏ phiếu nhạy cảm với thời gian). Chúng ta có thể xử lý nó một cách thông minh hơn một đối tượng khi nó tham lam yêu cầu cập nhật toàn bộ màn hình chỉ để hiển thị một thay đổi cụ thể cho riêng chính nó.

Giả sử chúng ta có một danh sách tất cả các bản gốc đồ họa có thể có mà chương trình của chúng ta có khả năng tạo ra và chúng ta buộc mỗi bản gốc với một tập hợp các câu lệnh điều kiện

if (iWantGreenCircle && iWantBigCircle && iWantOutlineOnMyCircle) ...

Tất nhiên, đây là một trừu tượng và thực sự, tập hợp các điều kiện đại diện cho một sinh vật nguyên thủy cụ thể đang bật / tắt có thể lớn (có lẽ hàng trăm cờ đều phải đánh giá là true).

Nếu chúng ta chạy chương trình, chúng ta có thể vẽ ra màn hình về cơ bản cùng một tốc độ mà chúng ta có thể đánh giá tất cả các điều kiện này. (Trường hợp xấu nhất: mất bao lâu để đánh giá tập hợp các câu lệnh điều kiện lớn nhất.)

Giờ đây, đối với bất kỳ trạng thái nào trong chương trình, chúng ta có thể đánh giá tất cả các điều kiện và xuất ra màn hình một cách nhanh chóng! (Chúng tôi biết hình dạng nguyên thủy của chúng tôi và các câu lệnh if phụ thuộc của chúng.)

Điều này giống như mua một trò chơi đồ họa có cường độ cao. Chỉ thay vì cài đặt nó vào ổ cứng và chạy nó qua bộ xử lý, bạn mua một bo mạch hoàn toàn mới chứa toàn bộ trò chơi và nhận làm đầu vào: chuột, bàn phím và đầu ra: màn hình. Đánh giá có điều kiện cực kỳ cô đọng (như dạng cơ bản nhất của điều kiện là các cổng logic trên bảng mạch). Điều này, tự nhiên, sẽ rất nhạy, nhưng nó hầu như không hỗ trợ sửa lỗi, vì toàn bộ thiết kế bảng mạch thay đổi khi bạn thực hiện một thay đổi thiết kế nhỏ (bởi vì "thiết kế" khác xa với bản chất của bảng mạch ). Với cái giá phải trả là tính linh hoạt và rõ ràng trong cách chúng tôi trình bày dữ liệu bên trong, chúng tôi đã đạt được "khả năng đáp ứng" đáng kể bởi vì chúng tôi không còn thực hiện "suy nghĩ" trong máy tính; tất cả chỉ là cho bảng mạch dựa trên các đầu vào.

Bài học, theo tôi hiểu, là phân chia lao động sao cho bạn cung cấp cho từng bộ phận của hệ thống (không nhất thiết chỉ là máy tính và màn hình) một thứ mà nó có thể làm tốt. "Tư duy máy tính" có thể được thực hiện dưới dạng các khái niệm như đồ vật ... Bộ não máy tính sẽ sẵn sàng thử và suy nghĩ thông suốt điều này cho bạn, nhưng bạn có thể đơn giản hóa công việc rất nhiều nếu bạn có thể để máy tính suy nghĩ điều khoản cập nhật dữ liệu và các khoảng thời gian có điều kiện. Việc con người của chúng ta trừu tượng hóa các khái niệm thành mã là duy tâm, và trong trường hợp của chương trình nội bộ, các phương pháp vẽ các phương pháp hơi quá lý tưởng. Khi tất cả những gì bạn muốn là một kết quả (mảng pixel có giá trị màu chính xác) và bạn có một chiếc máy có thể dễ dàng tạo ra một mảng lớn cứ sau 1/60 giây, cố gắng loại bỏ càng nhiều suy nghĩ hoa mỹ khỏi bộ não máy tính càng tốt để bạn có thể tập trung vào những gì bạn thực sự muốn: đồng bộ hóa các bản cập nhật đồ họa với đầu vào (nhanh) và hành vi tự nhiên của màn hình.

Làm thế nào để ánh xạ này đến các ứng dụng khác? Tôi muốn nghe về những ví dụ khác, nhưng tôi chắc rằng có rất nhiều. Tôi nghĩ rằng bất kỳ thứ gì cung cấp một "cửa sổ" thời gian thực cho trạng thái thông tin của bạn (trạng thái có thể thay đổi hoặc một cái gì đó như cơ sở dữ liệu ... màn hình chỉ là một cửa sổ vào bộ đệm hiển thị của bạn) đều có thể được hưởng lợi từ những thông tin chi tiết này.


2
++ Tôi đánh giá cao sự đảm nhận của bạn. Đối với tôi, ban đầu đó là một nỗ lực để thực hiện hiển thị theo mô tả chương trình trên các thiết bị chậm (hãy nghĩ đến thiết bị đầu cuối văn bản từ xa 9600 baud), nơi về cơ bản nó sẽ thực hiện một sự khác biệt tự động và truyền tải các bản cập nhật tối thiểu. Sau đó, tôi đã bị ép tại sao không chỉ viết mã này bằng vũ lực. Câu trả lời: bởi vì nếu dạng bề mặt của mã giống như một lớp sơn đơn giản , thì nó ngắn hơn, gần như ít lỗi, do đó được thực hiện trong một phần nhỏ thời gian phát triển. (Đó là những gì tôi nghĩ về lợi ích của DSL.)
Mike Dunlavey

... Nỗ lực phát triển được giải phóng sau đó có thể được đầu tư lại vào các màn hình tinh vi hơn và năng động hơn, mà người dùng cảm thấy nhanh và hài lòng. Vì vậy, bạn nhận được nhiều giao diện người dùng hơn cho tiền của nhà phát triển.
Mike Dunlavey

... Ví dụ: ứng dụng này từ khoảng 10 năm trước: pharsight.com/products/prod_pts_using_dme.php
Mike Dunlavey

1
Điều này khiến tôi hiểu ... khi bạn nói về trò chơi máy tính. Trên thực tế, nhiều trò chơi được mã hóa giống như cách Mike làm giao diện người dùng. Danh sách cập nhật được tuần hoàn với mọi khung hình.
GS Falken

Một ví dụ có vẻ liên quan đến một số điều bạn đã nói là phát hiện xem một phím / nút đang bị giữ hay mới được phát hành. Thật dễ dàng để biết nếu một nút được nhấn hay không. Đó là giá trị true / false từ API cấp thấp của bạn. Để biết một phím có đang được nhấn hay không, bạn phải biết trước đó nó đang ở trạng thái nào. Nếu nó từ 0-> 1 thì nó chỉ được nhấn. nếu nó là 1-> 1 nó đang bị giữ lại, nếu nó từ 1-> 0 thì bạn vừa phát hành.
Joshua Hedges

3

Tôi thấy khái niệm này rất giống với các máy trạng thái của điện tử kỹ thuật số cổ điển. Đặc biệt là những người ghi nhớ kết quả trước đó của họ.

Máy có đầu ra tiếp theo phụ thuộc vào đầu vào hiện tại và đầu ra trước đó theo (MÃ CỦA BẠN TẠI ĐÂY). Đầu vào hiện tại này không là gì ngoài đầu ra trước đó + (NGƯỜI DÙNG, TƯƠNG TÁC TẠI ĐÂY).

Hãy lấp đầy một bề mặt với những chiếc máy như vậy, và nó sẽ được người dùng tương tác và đồng thời đại diện cho một lớp dữ liệu có thể thay đổi. Nhưng ở giai đoạn này, nó vẫn sẽ không hoạt động, chỉ phản ánh tương tác của người dùng với dữ liệu cơ bản.

Tiếp theo, kết nối các máy trên bề mặt của bạn, để chúng chia sẻ ghi chú, theo (MÃ CỦA BẠN TẠI ĐÂY) và bây giờ chúng tôi làm cho nó trở nên thông minh. Nó sẽ trở thành một hệ thống máy tính tương tác.

Vì vậy, bạn chỉ cần cung cấp logic của bạn ở hai vị trí trong mô hình trên; phần còn lại do thiết kế máy tự lo. Đó là những gì tốt về nó.


1
Tôi dường như nhớ rằng tôi đã có một mô hình phần cứng khi điều này xảy ra với tôi.
Mike Dunlavey
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.