What we've been (a)waiting for? - Hana Dusíková - Meeting C++ 2023

แชร์
ฝัง
  • เผยแพร่เมื่อ 27 ธ.ค. 2024

ความคิดเห็น • 10

  • @AwesomeCplusplus
    @AwesomeCplusplus 11 หลายเดือนก่อน +2

    excellent overview of co-routines that were introduced in C++20 feature set. Thanks Hana Dusíková for the insightful talk. We need more of such talks to help the C++ community learn about the features and capabilities.

  • @wolpumba4099
    @wolpumba4099 11 หลายเดือนก่อน +3

    *Summary 1/3*
    *Introduction and Background*
    - 0:01 Introduction to the talk, mentioning a grammar error and the speaker's role at Toyota and various committees.
    - 0:08 Speaker's upcoming position at Toyota and involvement in various technical working groups.
    - 0:20 Contributions to ISP committee and chairing a study group for compile-time programming.
    - 0:26 Recent push of reflection to the Evolution working group.
    - 0:33 Previous talks by the speaker on compile-time programming.
    *Compile Time Programming and JavaScript Focus*
    - 0:40 Discussion about a past talk on compile time shell and its implementation.
    - 0:47 Implementation of functions in compile time for learning purposes.
    - 0:53 Hint towards the current talk's focus on JavaScript.
    - 0:59 Speaker's affinity for JavaScript despite being at a different conference.
    - 1:06 Audience engagement on their preference for JavaScript.
    *JavaScript's Capabilities and Search Engine Creation*
    - 1:18 Ease of prototyping and getting things done in JavaScript.
    - 1:25 Discussion on socket handling and memory allocation in JavaScript.
    - 1:31 Creation of a search engine during a committee meeting and a conference.
    - 1:43 The search engine's functionality in searching the C++ draft.
    - 1:56 Example of using the search engine to find information.
    - 2:05 Definition and backtracking in the search engine's interface.
    *Technical Challenges and Comparisons*
    - 2:22 Humorous remark about the specification of 'volatile' in programming.
    - 2:33 Technical issues with the presentation.
    - 2:39 Description of the search engine's algorithm and its simplicity.
    - 2:54 Details on the algorithm's operation and its use of a reverse index.
    - 3:08 Merging of document lists to provide search results.
    - 3:16 Laziness in JavaScript's implementation of the algorithm.
    - 3:23 Comparison of JavaScript and C++ in handling HTTP requests.
    - 3:39 Ease of using fetch in JavaScript for HTTP requests.
    - 3:50 Challenges in implementing similar functionality in C++.
    - 3:56 Limitations of using certain C++ libraries for HTTP requests.
    *C++ and Coral Library Utilization*
    - 4:04 Difficulties with string handling in C++ libraries.
    - 4:09 Complexities of networking in C++ compared to JavaScript.
    - 4:15 Differences in focus between C++ (performance) and JavaScript (ease of use).
    - 4:21 Discussion of the DLS Network API in C++.
    - 4:28 Decision to use the Coral library for implementation.
    - 4:39 Returning to the topic of JavaScript's capabilities.
    - 4:48 JavaScript's handling of asynchronous operations and promises.
    - 4:54 Single-threaded nature of JavaScript and its asynchronous execution.
    - 5:00 Lack of threading in JavaScript compared to other languages.
    *Audience Engagement and Library Discussion*
    - 5:13 JavaScript's 'await' keyword and its function.
    - 5:25 Discussion on syntax highlighting and the use of Coral library in C++.
    - 5:39 Audience engagement regarding the use of the Coral library.
    - 5:44 Percentage of audience familiar with Coral library.
    - 5:51 The user-friendliness of Coral's API.
    - 5:59 Experience of C++ developers with writing wrappers for the Coral library.
    *Coral Library Features and IPFS Introduction*
    - 6:08 Coral's legacy and its simplicity.
    - 6:19 Coral's support for various protocols including HTTP and FTP.
    - 6:26 Introduction of the interplanetary file system (IPFS).
    - 6:42 Explanation of Coral library's initialization and error handling.
    - 6:53 Setting options in Coral and handling HTTP redirections.
    - 7:01 Description of the Coral library's perform function.
    - 7:07 Error checking and memory management in Coral.
    *Handling Results and Coroutine Implementation in C++*
    - 7:16 Writing results into an object in C++ using Coral.
    - 7:24 Appending output to a string and handling return sizes.
    - 7:30 Lambda function usage in C++ with Coral.
    - 7:37 Example of creating and using a handle in Coral.
    - 7:44 Setting options and performing IO operations in Coral.
    - 7:49 Error handling and returning results in the custom wrapper.
    - 7:55 Asynchronous implementation desires in C++.
    - 8:00 Comparison of coroutines and functions in C++.
    - 8:08 Multiple entry points and suspension in coroutines.
    - 8:15 Discussion on the lifetime and resumption of coroutines.
    - 8:21 Challenges with memory allocation and optimization in coroutines.
    *Handling Results and Coroutine Implementation in C++*
    - 8:27 Complexity of creating coroutine types in the standard library.
    - 8:34 Example of a JavaScript function and its coroutine equivalent.
    - 8:40 Discussion on standard library support for coroutines.
    - 8:48 Industry reluctance to adopt new features like coroutines.
    - 8:55 The distinction between functions and coroutines in terms of execution and lifetime.
    - 9:03 Comparison of coroutines with threads in terms of execution context and overhead.
    - 9:10 The responsibility of developers in managing coroutines.
    - 9:17 Difficulty in standardizing coroutine types due to varied uses and designs.
    - 9:24 The chicken-and-egg problem in coroutine adoption and library support.
    - 9:31 Discussion on the non-blocking nature of JavaScript functions and the use of coroutines.
    *Handling Results and Coroutine Implementation in C++*
    - 16:20 Checking for exceptions in C++ and handling the 'asn' keyword.
    - 16:26 Differences in handling promises in C++ compared to other languages.
    - 16:33 Comparing divide functions in C++ and other languages.
    - 16:41 Lack of an 'asin' keyword in C++ and committee decisions on coroutine keywords.
    - 16:54 Introduction of coroutine keywords like 'co_return', 'co_yield', and 'co_await'.
    *Compiler Mechanics and Coroutine Traits*
    - 17:01 Compiler hints for identifying coroutines in C++.
    - 17:07 Explanation of Promise type and coroutine traits in C++.
    - 17:13 Internal workings of the compiler with coroutines.
    - 17:21 Difference between normal functions and coroutines from the caller's perspective.
    - 17:26 Header differences between normal functions and coroutines.
    - 17:31 How C++ compiles coroutines internally.
    *Coroutine Handles and Traits Specialization*
    - 17:37 Coroutine handle as a pointer to the coroutine.
    - 17:43 Requirement to use coroutine keywords for defining coroutines.
    - 17:51 Compiler checks for coroutine traits based on return type and arguments.
    - 17:59 Specialization points in coroutine traits.
    - 18:04 Complexity in understanding specialization points in coroutines.
    - 18:12 Structure of coroutine traits in C++.
    - 18:28 Default behavior of coroutine traits.
    - 18:34 Possibility to create different types of coroutines.
    *Coroutine Handles and Memory Management*
    - 18:48 Model differences between program stack and coroutine.
    - 19:01 Description of coroutine handle and coroutine object.
    - 19:06 Explanation of resumption point, promise type, and coroutine frame.
    - 19:12 Role of coroutine frame in storing variables across suspension points.
    - 19:18 Memory optimization in coroutine frames.
    - 19:24 Coroutine handle as a type-agnostic pointer.
    - 19:31 Standard library provision of coroutine handle functions.
    - 19:38 Polymorphic nature of coroutine handles for combining different types.
    - 19:44 Responsibility of managing coroutine handle destruction.
    *Coroutine Execution and Suspension Handling*
    - 19:50 Functionality of the resume function in coroutines.
    - 19:55 Process of resuming a coroutine and handling variables.
    - 20:01 Checking if a coroutine has finished execution.
    - 20:07 Accessing the promise type from a typed coroutine handle.
    - 20:15 Conversion between coroutine handle and void* pointer.
    - 20:22 Interacting with C-style APIs using coroutine handles.
    - 20:28 Implementation of a function as a coroutine.
    - 20:33 Structure of the function type in the context of coroutines.
    - 20:39 Lifecycle management of coroutine objects within functions.
    - 20:45 Accessing coroutine results through 'get' or explicit casting.
    - 20:49 Comparison of coroutines with lambdas in terms of variable storage.
    - 20:54 Description of coroutine suspension points.
    *Advanced Coroutine Concepts*
    - 21:01 Usage of 'co_await' and 'co_yield' to mark suspension points.
    - 21:07 Compiler transformations of expressions into suspension points.
    - 21:13 Behavior of 'co_await' in evaluating and handling expressions.
    - 21:24 Handling suspension and resumption in coroutines.
    - 21:31 Graph-like structure of coroutines compared to function calls.
    - 21:37 Standard coroutine types for suspending and resuming.
    - 21:43 Difference between 'suspend always' and 'suspend never' in coroutines.
    - 21:49 Internal workings of promise types in coroutines.
    - 21:55 Handling initial and final suspension points in coroutines.
    - 22:01 Implementation of 'suspend never' for immediate execution.
    - 22:07 Decision making in final suspension points in coroutines.
    - 22:13 Storing results in the promise type for later access.
    - 22:19 Handling exceptions in coroutines.
    *Coroutine Lifecycle Management*
    - 22:25 Construction and destruction of coroutine objects.
    - 22:31 Error handling in coroutine functions.
    - 22:37 Transformation of a divide function into a coroutine.
    - 22:43 Example of implementing a coroutine in C++.
    - 22:49 Pseudo-code for coroutine implementation.
    - 22:55 Handling initial suspension and body execution in coroutines.
    - 23:01 Exception handling within coroutine promise types.
    - 23:07 Final suspension and destruction of coroutines.
    - 23:13 Undefined behavior in resuming coroutines after final suspension.
    - 23:19 Discussion on the distinction between promise and promise types in coroutines.

    • @wolpumba4099
      @wolpumba4099 11 หลายเดือนก่อน

      *Summary 2/3*
      *Compiler Mechanics and Coroutine Traits*
      - 17:01 Compiler hints for identifying coroutines in C++.
      - 17:07 Explanation of Promise type and coroutine traits in C++.
      - 17:13 Internal workings of the compiler with coroutines.
      - 17:21 Difference between normal functions and coroutines from the caller's perspective.
      - 17:26 Header differences between normal functions and coroutines.
      - 17:31 How C++ compiles coroutines internally.
      *Coroutine Handles and Traits Specialization*
      - 17:37 Coroutine handle as a pointer to the coroutine.
      - 17:43 Requirement to use coroutine keywords for defining coroutines.
      - 17:51 Compiler checks for coroutine traits based on return type and arguments.
      - 17:59 Specialization points in coroutine traits.
      - 18:04 Complexity in understanding specialization points in coroutines.
      - 18:12 Structure of coroutine traits in C++.
      - 18:28 Default behavior of coroutine traits.
      - 18:34 Possibility to create different types of coroutines.
      *Coroutine Handles and Memory Management*
      - 18:48 Model differences between program stack and coroutine.
      - 19:01 Description of coroutine handle and coroutine object.
      - 19:06 Explanation of resumption point, promise type, and coroutine frame.
      - 19:12 Role of coroutine frame in storing variables across suspension points.
      - 19:18 Memory optimization in coroutine frames.
      - 19:24 Coroutine handle as a type-agnostic pointer.
      - 19:31 Standard library provision of coroutine handle functions.
      - 19:38 Polymorphic nature of coroutine handles for combining different types.
      - 19:44 Responsibility of managing coroutine handle destruction.
      *Coroutine Execution and Suspension Handling*
      - 19:50 Functionality of the resume function in coroutines.
      - 19:55 Process of resuming a coroutine and handling variables.
      - 20:01 Checking if a coroutine has finished execution.
      - 20:07 Accessing the promise type from a typed coroutine handle.
      - 20:15 Conversion between coroutine handle and void* pointer.
      - 20:22 Interacting with C-style APIs using coroutine handles.
      - 20:28 Implementation of a function as a coroutine.
      - 20:33 Structure of the function type in the context of coroutines.
      - 20:39 Lifecycle management of coroutine objects within functions.
      - 20:45 Accessing coroutine results through 'get' or explicit casting.
      - 20:49 Comparison of coroutines with lambdas in terms of variable storage.
      - 20:54 Description of coroutine suspension points.
      *Advanced Coroutine Concepts*
      - 21:01 Usage of 'co_await' and 'co_yield' to mark suspension points.
      - 21:07 Compiler transformations of expressions into suspension points.
      - 21:13 Behavior of 'co_await' in evaluating and handling expressions.
      - 21:24 Handling suspension and resumption in coroutines.
      - 21:31 Graph-like structure of coroutines compared to function calls.
      - 21:37 Standard coroutine types for suspending and resuming.
      - 21:43 Difference between 'suspend always' and 'suspend never' in coroutines.
      - 21:49 Internal workings of promise types in coroutines.
      - 21:55 Handling initial and final suspension points in coroutines.
      - 22:01 Implementation of 'suspend never' for immediate execution.
      - 22:07 Decision making in final suspension points in coroutines.
      - 22:13 Storing results in the promise type for later access.
      - 22:19 Handling exceptions in coroutines.
      *Coroutine Lifecycle Management*
      - 22:25 Construction and destruction of coroutine objects.
      - 22:31 Error handling in coroutine functions.
      - 22:37 Transformation of a divide function into a coroutine.
      - 22:43 Example of implementing a coroutine in C++.
      - 22:49 Pseudo-code for coroutine implementation.
      - 22:55 Handling initial suspension and body execution in coroutines.
      - 23:01 Exception handling within coroutine promise types.
      - 23:07 Final suspension and destruction of coroutines.
      - 23:13 Undefined behavior in resuming coroutines after final suspension.
      - 23:19 Discussion on the distinction between promise and promise types in coroutines.
      *Coroutine Properties and Management in C++*
      - 32:02 Discussing the non-copyable but movable nature of a certain type in the code.
      - 32:09 Intent to destroy a type if it's not empty and assignability of the promise from coroutine.
      - 32:16 Availability of the handle in a coroutine and readiness based on completion or exceptions.
      - 32:23 Handling suspension in coroutines with arguments.
      - 32:35 Examining promise type in coroutine suspension cases.
      *Coroutine Flexibility and Interaction*
      - 32:43 Ability to jump to other tasks while a coroutine is suspended.
      - 32:55 Checking for exceptions and retrieving results upon coroutine resumption.
      - 33:02 Details of the promise type in the context of coroutines.
      - 33:14 Similarity of function promise type with a few differences.
      - 33:19 Storing coroutine handle of an awaiting coroutine.
      *Coroutine Execution and Scheduling*
      - 33:25 Jumping to other coroutines at the final suspend point.
      - 33:30 Immediate start of coroutines associated with Coral library.
      - 33:42 Handling of final suspension in coroutines.
      - 33:50 Never being ready at the suspend point implies suspension.
      - 34:06 Describing the suspension process in coroutines.
      - 34:13 Notifying the scheduler upon coroutine suspension.
      - 34:23 Handling awaiting or empty states in coroutine promise types.
      *Coroutine Resumption and Specialized Functions*
      - 34:29 Resuming awaited coroutines once the result is ready.
      - 34:35 Use of a special suspend always return type.
      - 34:41 Storing polymorphic coroutine handle in the promise type.
      - 34:48 Interaction with the scheduler in coroutine suspension.
      - 35:01 Lazy perform function in coroutines, always being not ready.
      *Coroutine Integration with Coral Library*
      - 35:09 Associating coroutine handles with Coral handles.
      - 35:14 Utilizing private pointer options in coroutine handles.
      - 35:22 Instructing the scheduler to suspend the coroutine.
      - 35:47 Scheduling different handles based on coroutine suspension.
      *Coroutine Handling and Error Management*
      - 36:02 Returning results containing error codes from Coral.
      - 36:07 Handling errors and completion states in coroutines.
      - 36:14 Distinction between immediate and lazy coroutines.
      - 36:26 Preference for immediate coroutines, similar to JavaScript.
      *Coroutine Execution Styles and Implementation*
      - 36:33 Explanation of coroutine execution styles in JavaScript and Python.
      - 36:46 Implementing immediate coroutine start, as preferred in JavaScript.
      - 36:52 Suspending coroutines when they are blocked.
      - 37:03 Scheduler's role in managing multiple coroutines.

    • @wolpumba4099
      @wolpumba4099 11 หลายเดือนก่อน

      *Summary 3/3*
      *Scheduler's Role in Coroutine Management*
      - 37:09 Starting multiple coroutines and handling their suspension.
      - 37:18 Scheduler learning about new coroutines and their states.
      - 37:24 Running additional coroutines and their suspension management.
      - 37:31 Scheduler awareness of multiple started coroutines.
      - 37:44 Coordinating coroutine execution based on their suspension states.
      - 37:52 Scheduler's handling of blocked coroutines.
      *Coroutine Task Completion and Scheduling*
      - 38:05 Scheduler's responsibility to complete IO tasks for blocked coroutines.
      - 38:13 Determining the first coroutine to finish its task.
      - 38:19 Scheduler's role in completing IO tasks for coroutines.
      - 38:27 Coordinating coroutine resumption based on task completion.
      - 38:33 Handling blocked coroutines and their resumption.
      - 38:41 Scheduler's involvement in completing tasks for different coroutines.
      - 38:47 Coordination of coroutines and their completion order.
      - 38:54 Resuming coroutines based on their completion status.
      - 39:05 Starting multiple jobs in coroutines and handling slow IO work later.
      *Advanced Scheduler Features and Coroutine Integration*
      - 39:16 Description of the schedule type in the library.
      - 39:27 Variables for running and blocked tasks in the scheduler.
      - 39:33 Incrementing running tasks when starting a coroutine.
      - 39:40 Decrementing running tasks and selecting the next coroutine.
      - 39:47 Handling suspension and task selection in coroutines.
      - 39:53 Final suspension handling and hint passing in coroutines.
      - 40:04 Selecting the next coroutine based on hints or task status.
      - 40:10 Handling no-op coroutine returns when all tasks are blocked.
      - 40:16 Deciding coroutine actions based on running and blocked task numbers.
      - 40:25 Executing IO tasks when all coroutines are blocked.
      - 40:31 Returning handles of finished tasks from IO operations.
      - 40:37 Resuming now unblocked coroutines based on completed IO tasks.
      *Coroutine Integration with External Libraries*
      - 40:52 Using the multi API in lip curl for handling multiple handles.
      - 41:05 Perform operation in an infinite cycle for multi API.
      - 41:12 Handling finished tasks and extracting coroutine handles.
      - 41:19 Waiting for IO and retrying if tasks aren't finished.
      - 41:28 Waking up coroutines when downloads or timeouts occur.
      *Coroutine-Based Fetch Function Implementation*
      - 41:34 Fetch function for downloading content from a URL with retries.
      - 41:39 Handling output and exceptions in the fetch function.
      - 41:45 Using exponential back-off and coroutine suspension for retries.
      - 41:51 Throwing exceptions for non-successful HTTP response codes.
      - 41:58 Returning successful output to the user.
      *Managing Promises and Results in Coroutines*
      - 42:03 Utility functions for handling multiple promises in coroutines.
      - 42:10 Converting promises into vectors for result aggregation.
      - 42:16 Iterating over promises and awaiting their results.
      - 42:22 Using optional types for handling multiple results without exceptions.
      - 42:29 Building vectors of results based on optional success.
      - 42:35 Implementing range-based utilities for handling promises.
      - 42:41 Using operator piping for integrating with ranges in coroutines.
      *Coroutine Applications in Resource Management*
      - 42:47 Fetching resources and returning objects containing addresses and content.
      - 42:53 Function for fetching multiple resources and returning a vector of them.
      - 42:59 Downloading content in coroutines and handling their suspension states.
      *Development and Implementation of a Search Engine*
      - 47:54 Extraction of absolute links from a web page using CTR, transforming them into fetch resources.
      - 48:00 Transformation of URLs into fetch resources to download the resource.
      - 48:06 Creation of promises for resources and waiting for their completion.
      - 48:11 Receiving a vector of downloaded resources in parallel.
      - 48:19 Writing a search engine in JavaScript by extracting engrams and their positions.
      - 48:24 Implementation of a search function with a specified number of results.
      - 48:29 Processing text search by looking for sliding windows of engrams.
      - 48:35 Downloading engrams and shifting their positions.
      - 48:40 Sorting engrams by size and calculating intersections.
      - 48:48 Folding results to reduce them to document ID and count.
      - 48:54 Partial sorting of top results and resizing to return to the user.
      - 49:00 Building a search engine based on downloading engrams from the web.
      *Interactive Session and Audience Engagement*
      - 49:19 Example of a simple "Hello World" in C with secure bindings.
      - 49:42 Downloading comments, waiting for all to finish, and then evaluating.
      - 49:53 Invitation for audience questions.
      - 50:05 Availability of the speaker's library on GitHub and the search engine on GitHub Pages.
      - 50:23 Audience question about implementing 'any' to wait for the first coroutine to finish.
      - 50:41 Description of the 'select' function in JavaScript for handling coroutines.
      - 50:47 Need for modifications to the scheduler for implementing 'select'.
      *Scheduler Complexity and Coroutine Implementation*
      - 51:05 Mention of the function being available in the library but not shown in slides.
      - 51:17 Discussion about the complexity of modifying the scheduler.
      - 51:23 Difference between the scheduler on slides and in the actual library.
      - 51:28 Mention of the library not being perfect and limitations in handling multiple threads.
      - 51:36 Speaker's preference for single-threaded coroutine handling.
      - 51:43 Concept of thread-local schedulers for different coroutine trees.
      - 51:48 Idea for context-specific schedulers for coroutine management.
      *Immediate Execution and Coroutine Handling Strategies*
      - 52:01 Audience question about implementing immediate execution of coroutines.
      - 52:09 Explanation of immediate coroutine start and IO handling.
      - 52:17 Discussion on running as much as possible before needing to wait.
      - 52:24 Challenges in implementing this approach.
      - 52:29 Benefits of immediate execution for parallel downloads in a single thread.
      *Coroutine Models and Audience Interaction*
      - 52:46 Relevance of this model for business-type code.
      - 52:57 Reference to the sender-receiver model and lazy evaluation.
      - 53:04 Distinction between sender-receiver model and speaker's implementation.
      - 53:09 Discussion on different models and thread pools in coroutine handling.
      - 53:15 Mention of varying models in coroutine implementation.
      - 53:21 Audience question about prematurely stopping coroutines in a search engine.
      *Coroutine Cancellation and Safety Measures*
      - 53:26 Explanation of coroutine cancellation in non-running states.
      - 53:33 Safely destroying coroutines if not currently running.
      - 53:41 Handling new search inputs by stopping previous coroutine executions.
      - 53:47 Mechanism for coroutine cancellation through an external channel.
      - 54:00 Safely removing coroutine objects, even recursively.
      - 54:06 Ensuring safety in coroutine destruction when not actively running.
      - 54:11 Considerations for safely destroying coroutines in a single-thread model.
      - 54:17 Potential modifications needed for coroutine cancellation.
      *Advanced Coroutine Management and Memory Allocation*
      - 54:23 Adjustments to scheduler and promise handling for new search inputs.
      - 54:30 Implementation details for adapting coroutine management.
      - 54:36 Adjusting the scheduler to destroy specific coroutines.
      - 54:43 Minor code modifications required for handling new coroutine promises.
      - 54:49 Online audience question about overloading memory allocation in coroutines.
      - 55:02 Possibility of using custom allocators in coroutines.
      - 55:09 Providing allocators as arguments to promise types.
      - 55:15 Implementing custom memory allocation for coroutine promise types.
      - 55:23 Necessity to store allocator references in the promise type for safe destruction.
      - 55:29 Providing custom operator new and delete for coroutine promise types.
      - 55:35 Final thank you from the speaker and mention of the remaining conference days.
      Disclaimer: I used chatgpt4 to summarize the video transcript.

    • @pmcgee003
      @pmcgee003 11 หลายเดือนก่อน

      Thank you for your service. 🎉

  • @MatthewWalker0
    @MatthewWalker0 11 หลายเดือนก่อน +2

    the fetch joke got me!

  • @Carbon1911
    @Carbon1911 11 หลายเดือนก่อน

    28:11 what is a slideware? Thank you!

    • @hanadusikova
      @hanadusikova 11 หลายเดือนก่อน +1

      reveal.js with a few my own hacks to allow the scrolling of code (and in other presentations to allow also cute animations)

    • @piemur2000
      @piemur2000 11 หลายเดือนก่อน

      Slideware means that the code is not what you would actually use in a real implementation but a simplified version that helps understanding in the context of the presentation (slides). This is a comment to say to people to not just copy what is in the slide for their own usage as it will not work properly.

  • @domansharma5105
    @domansharma5105 11 หลายเดือนก่อน

    Engineer